Projet

Général

Profil

0001-opendatasoft-manage-HTTP-errors-56882.patch

Nicolas Roche, 13 septembre 2021 18:18

Télécharger (7,09 ko)

Voir les différences:

Subject: [PATCH] opendatasoft: manage HTTP errors (#56882)

 passerelle/apps/opendatasoft/models.py | 23 ++++++++++++++----
 tests/test_opendatasoft.py             | 33 ++++++++++++++++++++++++++
 tests/utils.py                         |  4 ++--
 3 files changed, 53 insertions(+), 7 deletions(-)
passerelle/apps/opendatasoft/models.py
14 14
# You should have received a copy of the GNU Affero General Public License
15 15
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
16 16

  
17 17
from django.db import models
18 18
from django.shortcuts import get_object_or_404
19 19
from django.urls import reverse
20 20
from django.utils.six.moves.urllib import parse as urlparse
21 21
from django.utils.translation import ugettext_lazy as _
22
from requests import RequestException
22 23

  
23 24
from passerelle.base.models import BaseQuery, BaseResource
24 25
from passerelle.utils.api import endpoint
25 26
from passerelle.utils.jsonresponse import APIError
26 27
from passerelle.utils.templates import render_to_string, validate_template
27 28

  
28 29

  
29 30
class OpenDataSoft(BaseResource):
......
79 80
        elif sort:
80 81
            params['sort'] = sort
81 82
        if self.api_key:
82 83
            params['apikey'] = self.api_key
83 84
        if limit:
84 85
            params['rows'] = limit
85 86
        params.update(urlparse.parse_qs(filter_expression))
86 87

  
87
        result_response = self.requests.get(url, params=params)
88
        err_desc = result_response.json().get('error')
89
        if err_desc:
90
            raise APIError(err_desc, http_status=200)
88
        try:
89
            response = self.requests.get(url, params=params)
90
        except RequestException as e:
91
            raise APIError('OpenDataSoft error: %s' % e)
92
        try:
93
            json_response = response.json()
94
        except ValueError:
95
            json_response = None
96
        if json_response and json_response.get('error'):
97
            raise APIError(json_response.get('error'))
98
        try:
99
            response.raise_for_status()
100
        except RequestException as e:
101
            raise APIError('OpenDataSoft error: %s' % e)
102
        if not json_response:
103
            raise APIError('OpenDataSoft error: bad JSON response')
91 104

  
92 105
        result = []
93
        for record in result_response.json().get('records'):
106
        for record in json_response.get('records'):
94 107
            data = {}
95 108
            for key, value in record.get('fields').items():
96 109
                if key in ('id', 'text'):
97 110
                    key = 'original_%s' % key
98 111
                data[key] = value
99 112
            data['id'] = record.get('recordid')
100 113
            data['text'] = render_to_string(text_template, data).strip()
101 114
            result.append(data)
tests/test_opendatasoft.py
16 16
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
17 17

  
18 18
import json
19 19

  
20 20
import mock
21 21
import pytest
22 22
import utils
23 23
from django.contrib.auth.models import User
24
from requests.exceptions import ConnectionError
24 25
from test_manager import login
25 26

  
26 27
from passerelle.apps.opendatasoft.models import OpenDataSoft, Query
27 28
from passerelle.utils import import_site
28 29

  
29 30
pytestmark = pytest.mark.django_db
30 31

  
31 32

  
......
124 125
)
125 126

  
126 127

  
127 128
@pytest.fixture
128 129
def connector():
129 130
    return utils.setup_access_rights(
130 131
        OpenDataSoft.objects.create(
131 132
            slug='my_connector',
133
            service_url='http://www.example.net',
132 134
            api_key='my_secret',
133 135
        )
134 136
    )
135 137

  
136 138

  
137 139
@pytest.fixture
138 140
def query(connector):
139 141
    return Query.objects.create(
......
438 440
    mocked_get.return_value = utils.FakedResponse(content=json.dumps(content), status_code=200)
439 441
    resp = app.get(endpoint, params=params, status=200)
440 442
    assert resp.json['data'][0]['original_id'] == 'original id'
441 443
    assert resp.json['data'][0]['original_text'] == 'original text'
442 444
    assert (
443 445
        resp.json['data'][0]['text']
444 446
        == "7cafcd5c692773e8b863587b2d38d6be82e023d8 - original id - original text"
445 447
    )
448

  
449

  
450
def test_call_search_errors(app, connector):
451
    endpoint = utils.generic_endpoint_url('opendatasoft', 'search', slug=connector.slug)
452
    assert endpoint == '/opendatasoft/my_connector/search'
453
    url = connector.service_url + '/api/records/1.0/search/'
454

  
455
    # Connection error
456
    exception = ConnectionError('Remote end closed connection without response')
457
    with utils.mock_url(url=url, exception=exception):
458
        resp = app.get(endpoint)
459
    assert resp.json['err']
460
    assert resp.json['err_desc'] == 'OpenDataSoft error: Remote end closed connection without response'
461

  
462
    # API error, provides HTTP status code
463
    with utils.mock_url(url=url, response='{"error": "Unknown dataset: foo"}', status_code=404):
464
        resp = app.get(endpoint)
465
    assert resp.json['err']
466
    assert resp.json['err_desc'] == 'Unknown dataset: foo'
467

  
468
    # HTTP error
469
    with utils.mock_url(url=url, response='not a json content', reason='Not Found', status_code=404):
470
        resp = app.get(endpoint)
471
    assert resp.json['err']
472
    assert 'OpenDataSoft error: 404 Client Error: Not Found' in resp.json['err_desc']
473

  
474
    # bad JSON response
475
    with utils.mock_url(url=url, response='not a json content', status_code=200):
476
        resp = app.get(endpoint)
477
    assert resp.json['err']
478
    assert resp.json['err_desc'] == 'OpenDataSoft error: bad JSON response'
tests/utils.py
23 23

  
24 24
class FakedResponse(mock.Mock):
25 25
    headers = {}
26 26

  
27 27
    def json(self):
28 28
        return json_loads(self.content)
29 29

  
30 30

  
31
def mock_url(url=None, response='', status_code=200, headers=None, exception=None):
31
def mock_url(url=None, response='', status_code=200, headers=None, reason=None, exception=None):
32 32
    urlmatch_kwargs = {}
33 33
    if url:
34 34
        parsed = urlparse.urlparse(url)
35 35
        if parsed.netloc:
36 36
            urlmatch_kwargs['netloc'] = parsed.netloc
37 37
        if parsed.path:
38 38
            urlmatch_kwargs['path'] = parsed.path
39 39

  
40 40
    if not isinstance(response, str):
41 41
        response = json.dumps(response)
42 42

  
43 43
    @httmock.remember_called
44 44
    @httmock.urlmatch(**urlmatch_kwargs)
45 45
    def mocked(url, request):
46 46
        if exception:
47 47
            raise exception
48
        return httmock.response(status_code, response, headers, request=request)
48
        return httmock.response(status_code, response, headers, reason, request=request)
49 49

  
50 50
    return httmock.HTTMock(mocked)
51 51

  
52 52

  
53 53
def make_resource(model_class, **kwargs):
54 54
    resource = model_class.objects.create(**kwargs)
55 55
    setup_access_rights(resource)
56 56
    return resource
57
-