0001-opendatasoft-manage-HTTP-errors-56882.patch
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 |
- |