Projet

Général

Profil

0001-wcs-handle-network-error-on-requests-31438.patch

Lauréline Guérin, 05 octobre 2020 10:07

Télécharger (22,2 ko)

Voir les différences:

Subject: [PATCH] wcs: handle network error on requests (#31438)

 combo/apps/wcs/models.py |  90 +++++++++++-----------
 combo/apps/wcs/utils.py  |  22 ++++--
 tests/test_wcs.py        | 161 ++++++++++++++++++++++++++++++++++-----
 3 files changed, 203 insertions(+), 70 deletions(-)
combo/apps/wcs/models.py
27 27
from django.utils.translation import ugettext_lazy as _
28 28

  
29 29
from jsonfield import JSONField
30
from requests.exceptions import RequestException
30 31

  
31 32
from combo.data.models import CellBase, Page
32 33
from combo.data.library import register_cell_class
......
94 95
                wcs_site = get_wcs_services().get(wcs_key)
95 96
                forms_response_json = get_wcs_json(wcs_site, 'api/formdefs/')
96 97

  
97
                if not forms_response_json:
98
                if not forms_response_json or forms_response_json.get('err') == 1:
98 99
                    # can not retrieve data, don't report cell as invalid
99 100
                    self.mark_as_valid()
100 101
                    return
101 102

  
102
                if isinstance(forms_response_json, dict):
103
                    # forward compability with future w.c.s. API
104
                    forms_response_json = forms_response_json.get('data')
105

  
106 103
                form_found = False
107
                for form in forms_response_json or []:
104
                for form in forms_response_json.get('data') or []:
108 105
                    slug = form.get('slug')
109 106
                    if slug == form_slug:
110 107
                        self.cached_title = form.get('title')
......
200 197
                wcs_site = get_wcs_services().get(wcs_key)
201 198
                categories_response_json = get_wcs_json(wcs_site, 'api/categories/')
202 199

  
203
                if not categories_response_json:
200
                if not categories_response_json or categories_response_json.get('err') == 1:
204 201
                    # can not retrieve data, don't report cell as invalid
205 202
                    self.mark_as_valid()
206 203
                    return
207 204

  
208 205
                category_found = False
209
                for category in categories_response_json.get('data', []):
206
                for category in categories_response_json.get('data') or []:
210 207
                    slug = category.get('slug')
211 208
                    if slug == category_slug:
212 209
                        self.cached_title = category.get('title')
......
305 302
            wcs_site['base_url'] = url
306 303
            wcs_site['slug'] = wcs_slug
307 304

  
308
            response = requests.get(
309
                    api_url,
310
                    remote_service=wcs_site,
311
                    user=getattr(context.get('request'), 'user', None),
312
                    cache_duration=self.cache_duration,
313
                    raise_if_not_cached=not(context.get('synchronous')),
314
                    log_errors=False)
315
            returns.add(response.status_code)
316
            if response.status_code == 200:
317
                json_response = response.json()
318
                if isinstance(json_response, list):
319
                    # backward compat with older w.c.s.
320
                    wcs_site['data'] = json_response
321
                elif json_response.get('err', 0) == 0:
322
                    wcs_site['data'] = json_response['data']
323
                else:
324
                    # skip errors
325
                    continue
326
                # and mark all items with the site info
327
                for item in wcs_site['data']:
328
                    item['site_slug'] = wcs_slug
305
            try:
306
                response = requests.get(
307
                        api_url,
308
                        remote_service=wcs_site,
309
                        user=getattr(context.get('request'), 'user', None),
310
                        cache_duration=self.cache_duration,
311
                        raise_if_not_cached=not(context.get('synchronous')),
312
                        log_errors=False)
313
                returns.add(response.status_code)
314
                response.raise_for_status()
315
            except RequestException:
316
                continue
317
            json_response = response.json()
318
            if json_response.get('err', 0) == 0:
319
                wcs_site['data'] = json_response['data']
320
            else:
321
                # skip errors
322
                continue
323
            # and mark all items with the site info
324
            for item in wcs_site['data']:
325
                item['site_slug'] = wcs_slug
329 326

  
330
        if 200 not in returns:  # not a single valid answer
327
        if returns and 200 not in returns:  # not a single valid answer
331 328
            logging.debug('failed to get data from any %s (%r)', api_url, returns)
332 329
            if all([400 <= r < 500 for r in returns]) and self.warn_on_4xx:
333 330
                # only 4xx errors, not a user cell, report the cell as invalid
......
409 406
            wcs_site = get_wcs_services().get(wcs_key)
410 407
            categories_response_json = get_wcs_json(wcs_site, 'api/categories/')
411 408

  
412
            if not categories_response_json:
409
            if not categories_response_json or categories_response_json.get('err') == 1:
413 410
                # can not retrieve data, don't report cell as invalid
414 411
                continue
415 412

  
416 413
            category_found = any(
417 414
                [c.get('slug') == category_slug
418
                 for c in categories_response_json.get('data', [])])
415
                 for c in categories_response_json.get('data') or []])
419 416
            if not category_found:
420 417
                self.mark_as_invalid('wcs_category_not_found')
421 418
                return
......
616 613
        wcs_site = get_wcs_services().get(wcs_key)
617 614
        categories_response_json = get_wcs_json(wcs_site, 'api/categories/')
618 615

  
619
        if not categories_response_json:
616
        if not categories_response_json or categories_response_json.get('err') == 1:
620 617
            # can not retrieve data, don't report cell as invalid
621 618
            return
622 619

  
623 620
        category_found = any(
624 621
            [c.get('slug') == category_slug
625
             for c in categories_response_json.get('data', [])])
622
             for c in categories_response_json.get('data') or []])
626 623
        if not category_found:
627 624
            self.mark_as_invalid('wcs_category_not_found')
628 625
            return
......
754 751
                wcs_site = get_wcs_services().get(wcs_key)
755 752
                card_models = get_wcs_json(wcs_site, 'api/cards/@list')
756 753

  
757
                if not card_models:
754
                if not card_models or card_models.get('err') == 1:
758 755
                    # can not retrieve data, don't report cell as invalid
759 756
                    self.mark_as_valid()
760 757
                    return
761 758

  
762 759
                card_found = False
763
                for card in card_models.get('data', []):
760
                for card in card_models.get('data') or []:
764 761
                    slug = card['slug']
765 762
                    if slug == card_slug:
766 763
                        self.cached_title = card['title']
......
838 835
                if card_schema.get('err') == 1:
839 836
                    if card_schema.get('err_class') == 'Page not found':
840 837
                        self.mark_as_invalid('wcs_card_not_found')
838
                    else:
839
                        self.mark_as_valid()
841 840
                    return
842 841

  
843 842
                self.cached_title = card_schema['name']
......
862 861

  
863 862
        wcs_site = get_wcs_services().get(self.wcs_site)
864 863

  
865
        response = requests.get(
866
                api_url,
867
                remote_service=wcs_site,
868
                user=getattr(context.get('request'), 'user', None),
869
                cache_duration=5,
870
                raise_if_not_cached=not(context.get('synchronous')),
871
                log_errors=False)
864
        try:
865
            response = requests.get(
866
                    api_url,
867
                    remote_service=wcs_site,
868
                    user=getattr(context.get('request'), 'user', None),
869
                    cache_duration=5,
870
                    raise_if_not_cached=not(context.get('synchronous')),
871
                    log_errors=False)
872
            response.raise_for_status()
873
        except RequestException:
874
            return extra_context
875

  
872 876
        if response.status_code == 200:
873 877
            extra_context['card'] = response.json()
874 878

  
combo/apps/wcs/utils.py
16 16

  
17 17
from django.conf import settings
18 18

  
19
from requests.exceptions import RequestException
20

  
19 21
from combo.utils import requests
20 22

  
23

  
21 24
def is_wcs_enabled(cls):
22 25
    return hasattr(settings, 'KNOWN_SERVICES') and settings.KNOWN_SERVICES.get('wcs')
23 26

  
27

  
24 28
def get_wcs_services():
25 29
    if not is_wcs_enabled(None):
26 30
        return {}
......
32 36
        # no site specified (probably an import referencing a not yet deployed
33 37
        # site)
34 38
        return {'err': 1, 'err_desc': 'no-wcs-site'}
35
    response = requests.get(path, remote_service=wcs_site, without_user=True,
39
    try:
40
        response = requests.get(
41
            path, remote_service=wcs_site, without_user=True,
36 42
            headers={'accept': 'application/json'})
37
    if response.status_code != 200:
38
        # the exact error will have been logged during request.
39
        return {}
43
        response.raise_for_status()
44
    except RequestException:
45
        return {'err': 1, 'data': None}
40 46
    return response.json()
41 47

  
48

  
42 49
def get_wcs_options(url, include_category_slug=False):
43 50
    references = []
44 51
    for wcs_key, wcs_site in sorted(get_wcs_services().items(), key=lambda x: x[1]['title']):
45 52
        site_title = wcs_site.get('title')
46 53
        response_json = get_wcs_json(wcs_site, url)
47
        if type(response_json) is dict:
48
            if response_json.get('err') == 1:
49
                continue
50
            response_json = response_json.get('data')
54
        if response_json.get('err') == 1:
55
            continue
56
        response_json = response_json.get('data')
51 57
        if not response_json:
52 58
            continue
53 59
        for element in response_json:
tests/test_wcs.py
16 16
from django.utils.six.moves.urllib import parse as urlparse
17 17

  
18 18
import mock
19
from requests.exceptions import ConnectionError
20
from requests.models import Response
19 21

  
20 22
from combo.data.library import get_cell_classes
21 23
from combo.data.models import CellBase, LinkListCell, Page, ValidityInfo
......
272 274
    cell.save()
273 275
    assert ValidityInfo.objects.exists() is False
274 276

  
277
    # can not retrieve data, don't set cell as invalid
278
    with mock.patch('combo.apps.wcs.models.requests.get') as requests_get:
279
        mock_resp = Response()
280
        mock_resp.status_code = 500
281
        requests_get.return_value = mock_resp
282
        cell.save()
283
    assert ValidityInfo.objects.exists() is False
284
    with mock.patch('combo.apps.wcs.models.requests.get') as requests_get:
285
        requests_get.side_effect = ConnectionError()
286
        cell.save()
287
    assert ValidityInfo.objects.exists() is False
288

  
275 289
    # can not retrieve formdefs, don't set cell as invalid
276 290
    with mock.patch('combo.apps.wcs.models.requests.get') as requests_get:
277
        mock_json = mock.Mock(status_code=404)
278
        requests_get.return_value = mock_json
291
        mock_resp = Response()
292
        mock_resp.status_code = 404
293
        requests_get.return_value = mock_resp
279 294
        cell.save()
280 295
    assert ValidityInfo.objects.exists() is False
281 296

  
......
328 343
    cell.save()
329 344
    assert ValidityInfo.objects.exists() is False
330 345

  
346
    # can not retrieve data, don't set cell as invalid
347
    with mock.patch('combo.apps.wcs.models.requests.get') as requests_get:
348
        mock_resp = Response()
349
        mock_resp.status_code = 500
350
        requests_get.return_value = mock_resp
351
        cell.save()
352
    assert ValidityInfo.objects.exists() is False
353
    with mock.patch('combo.apps.wcs.models.requests.get') as requests_get:
354
        requests_get.side_effect = ConnectionError()
355
        cell.save()
356
    assert ValidityInfo.objects.exists() is False
357

  
331 358
    # can not retrieve categories, don't set cell as invalid
332 359
    with mock.patch('combo.apps.wcs.models.requests.get') as requests_get:
333
        mock_json = mock.Mock(status_code=404)
334
        requests_get.return_value = mock_json
360
        mock_resp = Response()
361
        mock_resp.status_code = 404
362
        requests_get.return_value = mock_resp
335 363
        cell.save()
336 364
    assert ValidityInfo.objects.exists() is False
337 365

  
......
474 502
    cell.get_data(context)
475 503
    assert ValidityInfo.objects.exists() is False
476 504

  
505
    # can not retrieve data, don't set cell as invalid
477 506
    with mock.patch('combo.apps.wcs.models.requests.get') as requests_get:
478
        mock_json = mock.Mock(status_code=404)
479
        requests_get.return_value = mock_json
507
        mock_resp = Response()
508
        mock_resp.status_code = 500
509
        requests_get.return_value = mock_resp
510
        cell.get_data(context)
511
    assert ValidityInfo.objects.exists() is False
512
    with mock.patch('combo.apps.wcs.models.requests.get') as requests_get:
513
        requests_get.side_effect = ConnectionError()
514
        cell.get_data(context)
515
    assert ValidityInfo.objects.exists() is False
516

  
517
    with mock.patch('combo.apps.wcs.models.requests.get') as requests_get:
518
        mock_resp = Response()
519
        mock_resp.status_code = 404
520
        requests_get.return_value = mock_resp
480 521
        cell.get_data(context)
481 522
    assert ValidityInfo.objects.exists() is False
482 523

  
......
496 537
    cell.check_validity()
497 538
    assert ValidityInfo.objects.exists() is False
498 539

  
540
    # can not retrieve data, don't set cell as invalid
541
    with mock.patch('combo.apps.wcs.models.requests.get') as requests_get:
542
        mock_resp = Response()
543
        mock_resp.status_code = 500
544
        requests_get.return_value = mock_resp
545
        cell.check_validity()
546
    assert ValidityInfo.objects.exists() is False
547
    with mock.patch('combo.apps.wcs.models.requests.get') as requests_get:
548
        requests_get.side_effect = ConnectionError()
549
        cell.check_validity()
550
    assert ValidityInfo.objects.exists() is False
551

  
499 552
    # can not retrieve categories, don't set cell as invalid
500 553
    with mock.patch('combo.apps.wcs.models.requests.get') as requests_get:
501
        mock_json = mock.Mock(status_code=404)
502
        requests_get.return_value = mock_json
554
        mock_resp = Response()
555
        mock_resp.status_code = 404
556
        requests_get.return_value = mock_resp
503 557
        cell.check_validity()
504 558
    assert ValidityInfo.objects.exists() is False
505 559

  
......
608 662
        cell.get_data(context)
609 663
    assert ValidityInfo.objects.exists() is False
610 664

  
665
    # can not retrieve data, don't set cell as invalid
666
    with mock.patch('combo.apps.wcs.models.requests.get') as requests_get:
667
        mock_resp = Response()
668
        mock_resp.status_code = 500
669
        requests_get.return_value = mock_resp
670
        cell.get_data(context)
671
    assert ValidityInfo.objects.exists() is False
672
    with mock.patch('combo.apps.wcs.models.requests.get') as requests_get:
673
        requests_get.side_effect = ConnectionError()
674
        cell.get_data(context)
675
    assert ValidityInfo.objects.exists() is False
676

  
611 677
    # can not retrieve data, don't set cell as invalid
612 678
    with mock.patch('combo.apps.wcs.models.requests.get') as requests_get:
613 679
        mock_json = mock.Mock(status_code=500)
......
723 789
        context.pop('combo_display_even_empty_categories')
724 790

  
725 791

  
726
def test_forms_of_category_cell_validity(context):
792
@mock.patch('combo.apps.wcs.utils.requests.send', side_effect=mocked_requests_send)
793
def test_forms_of_category_cell_validity(mock_send, context):
727 794
    page = Page.objects.create(title='xxx', slug='test_forms_of_category_cell_render', template_name='standard')
728 795
    cell = WcsFormsOfCategoryCell.objects.create(page=page, placeholder='content', order=0)
729 796
    validity_info = ValidityInfo.objects.latest('pk')
......
748 815
        requests_get.return_value = mock_json
749 816
        cell.render(context)
750 817
    assert ValidityInfo.objects.exists() is False
818
    with mock.patch('combo.apps.wcs.models.requests.get') as requests_get:
819
        requests_get.side_effect = ConnectionError()
820
        cell.render(context)
821
    assert ValidityInfo.objects.exists() is False
751 822

  
752 823
    # can not retrieve categories, don't set cell as invalid
753 824
    with mock.patch('combo.apps.wcs.models.requests.get') as requests_get:
754
        mock_json = mock.Mock(status_code=404)
755
        requests_get.return_value = mock_json
825
        mock_resp = Response()
826
        mock_resp.status_code = 404
827
        requests_get.return_value = mock_resp
756 828
        cell.save()
757 829
    assert ValidityInfo.objects.exists() is False
758 830

  
......
763 835
    assert validity_info.invalid_since is not None
764 836

  
765 837

  
766
def test_forms_of_category_cell_check_validity(context):
838
@mock.patch('combo.apps.wcs.utils.requests.send', side_effect=mocked_requests_send)
839
def test_forms_of_category_cell_check_validity(mock_send, context):
767 840
    page = Page.objects.create(title='xxx', slug='test_forms_of_category_cell_render', template_name='standard')
768 841
    cell = WcsFormsOfCategoryCell.objects.create(page=page, placeholder='content', order=0)
769 842

  
......
861 934
    cell.check_validity()
862 935
    assert ValidityInfo.objects.exists() is False
863 936

  
864
    # can not retrieve vategories, don't set cell as invalid
937
    # can not retrieve data, don't set cell as invalid
865 938
    with mock.patch('combo.apps.wcs.models.requests.get') as requests_get:
866
        mock_json = mock.Mock(status_code=404)
867
        requests_get.return_value = mock_json
939
        mock_resp = Response()
940
        mock_resp.status_code = 500
941
        requests_get.return_value = mock_resp
942
        cell.check_validity()
943
    assert ValidityInfo.objects.exists() is False
944
    with mock.patch('combo.apps.wcs.models.requests.get') as requests_get:
945
        requests_get.side_effect = ConnectionError()
946
        cell.check_validity()
947
    assert ValidityInfo.objects.exists() is False
948

  
949
    # can not retrieve categories, don't set cell as invalid
950
    with mock.patch('combo.apps.wcs.models.requests.get') as requests_get:
951
        mock_resp = Response()
952
        mock_resp.status_code = 404
953
        requests_get.return_value = mock_resp
868 954
        cell.check_validity()
869 955
    assert ValidityInfo.objects.exists() is False
870 956

  
......
984 1070
    cell.save()
985 1071
    assert ValidityInfo.objects.exists() is False
986 1072

  
1073
    # can not retrieve data, don't set cell as invalid
1074
    with mock.patch('combo.apps.wcs.models.requests.get') as requests_get:
1075
        mock_resp = Response()
1076
        mock_resp.status_code = 500
1077
        requests_get.return_value = mock_resp
1078
        cell.save()
1079
    assert ValidityInfo.objects.exists() is False
1080
    with mock.patch('combo.apps.wcs.models.requests.get') as requests_get:
1081
        requests_get.side_effect = ConnectionError()
1082
        cell.save()
1083
    assert ValidityInfo.objects.exists() is False
1084

  
987 1085
    # can not retrieve carddefs, don't set cell as invalid
988 1086
    with mock.patch('combo.apps.wcs.models.requests.get') as requests_get:
989
        mock_json = mock.Mock(status_code=404)
990
        requests_get.return_value = mock_json
1087
        mock_resp = Response()
1088
        mock_resp.status_code = 404
1089
        requests_get.return_value = mock_resp
991 1090
        cell.save()
992 1091
    assert ValidityInfo.objects.exists() is False
993 1092

  
......
1081 1180
    cell.save()
1082 1181
    assert ValidityInfo.objects.exists() is False
1083 1182

  
1183
    # can not retrieve data, don't set cell as invalid
1184
    with mock.patch('combo.apps.wcs.models.requests.get') as requests_get:
1185
        mock_resp = Response()
1186
        mock_resp.status_code = 500
1187
        requests_get.return_value = mock_resp
1188
        cell.save()
1189
    assert ValidityInfo.objects.exists() is False
1190
    with mock.patch('combo.apps.wcs.models.requests.get') as requests_get:
1191
        requests_get.side_effect = ConnectionError()
1192
        cell.save()
1193
    assert ValidityInfo.objects.exists() is False
1194

  
1084 1195
    # can not retrieve carddefs, don't set cell as invalid
1085 1196
    with mock.patch('combo.apps.wcs.models.requests.get') as requests_get:
1086
        mock_json = mock.Mock(status_code=404)
1087
        requests_get.return_value = mock_json
1197
        mock_resp = Response()
1198
        mock_resp.status_code = 404
1199
        requests_get.return_value = mock_resp
1088 1200
        cell.save()
1089 1201
    assert ValidityInfo.objects.exists() is False
1090 1202

  
......
1135 1247

  
1136 1248
    context['synchronous'] = True  # to get fresh content
1137 1249

  
1250
    with mock.patch('combo.apps.wcs.models.requests.get') as requests_get:
1251
        mock_resp = Response()
1252
        mock_resp.status_code = 500
1253
        requests_get.return_value = mock_resp
1254
        result = cell.render(context)
1255
    assert '<p>Unknown Card</p>' in result
1256
    with mock.patch('combo.apps.wcs.models.requests.get') as requests_get:
1257
        requests_get.side_effect = ConnectionError()
1258
        result = cell.render(context)
1259
    assert '<p>Unknown Card</p>' in result
1260

  
1138 1261
    result = cell.render(context)
1139 1262
    assert '<h2>Card Model 1 - aa</h2>' in result
1140 1263
    assert '<span class="label">Field A</span>\n    \n    <span class="value">a</span>' in result
1141
-