Projet

Général

Profil

0002-toulouse_smart-do-not-crash-on-unknown-property-type.patch

Nicolas Roche, 27 janvier 2022 12:04

Télécharger (6,27 ko)

Voir les différences:

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(+)
passerelle/contrib/toulouse_smart/models.py
177 177
            block = post_data['fields']['%s_raw' % wcs_block_varname][0]
178 178
        except (KeyError, TypeError):
179 179
            block = {}
180 180
        data = {}
181 181
        cast = {'string': str, 'int': int, 'boolean': bool, 'item': str}
182 182
        for prop in intervention_type.get('properties') or []:
183 183
            varname = slugify(prop['name']).replace('-', '_')
184 184
            if block.get(varname):
185
                if prop['type'] not in cast:
186
                    raise APIError(
187
                        'unmanaged "%s" type for the %s/%s property'
188
                        % (prop['type'], intervention_type['name'], prop['name']),
189
                        http_status=400,
190
                    )
185 191
                try:
186 192
                    data[prop['name']] = cast[prop['type']](block[varname])
187 193
                except ValueError:
188 194
                    raise APIError(
189 195
                        "cannot cast '%s' field to %s : '%s'" % (varname, cast[prop['type']], block[varname]),
190 196
                        http_status=400,
191 197
                    )
192 198
            elif prop['required']:
tests/test_toulouse_smart.py
155 155
              <displayName>Champ 2</displayName>
156 156
              <type>int</type>
157 157
              <required>true</required>
158 158
           </properties>
159 159
           <properties>
160 160
              <name>IGNORED-FIELD-HAVING-NO-TYPE</name>
161 161
              <displayName>Champ 3</displayName>
162 162
           </properties>
163
          <properties>
164
              <name>FIELD3</name>
165
              <displayName>Champ 3</displayName>
166
              <type>plop</type>
167
           </properties>
163 168
       </properties>
164 169
   </item>
165 170
   <item>
166 171
       <id>0002</id>
167 172
       <name>empty</name>
168 173
   </item>
169 174
</List>'''.encode()
170 175

  
......
181 186
                    'name': 'TYPE-OBJET',
182 187
                    'displayName': 'Champ 1',
183 188
                    'required': False,
184 189
                    'type': 'item',
185 190
                    'defaultValue': 'Ne sait pas',
186 191
                    'restrictedValues': ['Candélabre', 'Mât', 'Ne sait pas'],
187 192
                },
188 193
                {'name': 'FIELD2', 'displayName': 'Champ 2', 'required': True, 'type': 'int'},
194
                {'name': 'FIELD3', 'displayName': 'Champ 3', 'required': False, 'type': 'plop'},
189 195
            ],
190 196
        },
191 197
        {
192 198
            'id': '0002',
193 199
            'name': 'empty',
194 200
            'order': 2,
195 201
        },
196 202
    ]
......
221 227
def test_manage_intervention_types(app, smart, admin_user):
222 228
    login(app)
223 229
    resp = app.get('/manage' + URL + 'type-intervention/')
224 230
    assert [[td.text for td in tr.cssselect('td,th')] for tr in resp.pyquery('tr')] == [
225 231
        ["Nom du type d'intervention", 'Nom', 'Type', 'Requis', 'Valeur par défaut'],
226 232
        ['1 - coin'],
227 233
        [None, 'TYPE-OBJET', 'item («Candélabre», «Mât», «Ne sait pas»)', '✘', 'Ne sait pas'],
228 234
        [None, 'FIELD2', 'int', '✔', None],
235
        [None, 'FIELD3', 'plop', '✘', None],
229 236
        ['2 - empty'],
230 237
    ]
231 238
    resp = resp.click('Export to blocks')
232 239
    with zipfile.ZipFile(io.BytesIO(resp.body)) as zip_file:
233 240
        assert zip_file.namelist() == ['block-coin.wcs']
234 241
        with zip_file.open('block-coin.wcs') as fd:
235 242
            content = ET.tostring(ET.fromstring(fd.read()), pretty_print=True).decode()
236 243
            assert (
......
264 271
      <display_locations>
265 272
        <display_location>validation</display_location>
266 273
        <display_location>summary</display_location>
267 274
      </display_locations>
268 275
      <validation>
269 276
        <type>digits</type>
270 277
      </validation>
271 278
    </field>
279
    <field>
280
      <id>66c8a74e-cd26-eb34-2bec-9c0c4ec43841</id>
281
      <label>Champ 3</label>
282
      <type>plop</type>
283
      <required>False</required>
284
      <varname>field3</varname>
285
      <display_locations>
286
        <display_location>validation</display_location>
287
        <display_location>summary</display_location>
288
      </display_locations>
289
    </field>
272 290
  </fields>
273 291
</block>
274 292
'''
275 293
            )
276 294

  
277 295

  
278 296
INTERVENTION_ID = json.loads(get_json_file('create_intervention'))['id']
279 297

  
......
486 504
def test_create_intervention_cast_error(app, smart):
487 505
    payload = deepcopy(CREATE_INTERVENTION_PAYLOAD)
488 506
    payload['fields']['coin_raw'][0]['field2'] = 'not-an-integer'
489 507
    resp = app.post_json(URL + 'create-intervention/', params=payload, status=400)
490 508
    assert resp.json['err']
491 509
    assert "cannot cast 'field2' field to <class 'int'>" in resp.json['err_desc']
492 510

  
493 511

  
512
@mock_response(
513
    ['/v1/type-intervention', None, INTERVENTION_TYPES],
514
)
515
@mock.patch("django.db.models.fields.UUIDField.get_default", return_value=UUID)
516
def test_create_intervention_unmanaged_type_to_cast(mocked_uuid4, app, smart):
517
    payload = deepcopy(CREATE_INTERVENTION_PAYLOAD)
518
    payload['fields']['coin_raw'][0]['field3'] = 'plop value'
519
    resp = app.post_json(URL + 'create-intervention/', params=payload, status=400)
520
    assert resp.json['err']
521
    assert resp.json['err_desc'] == 'unmanaged "plop" type for the coin/FIELD3 property'
522

  
523

  
494 524
@mock_response(
495 525
    ['/v1/type-intervention', None, INTERVENTION_TYPES],
496 526
)
497 527
def test_create_intervention_missing_value(app, smart):
498 528
    field_payload = {
499 529
        'coin_raw': [
500 530
            {
501 531
                'type_objet': 'Candélabre',
502
-