0001-signal_arretes-create-signal-arretes-connector-65822.patch
passerelle/apps/signal_arretes/migrations/0001_initial.py | ||
---|---|---|
1 |
# Generated by Django 2.2.26 on 2022-06-13 14:13 |
|
2 | ||
3 |
from django.db import migrations, models |
|
4 | ||
5 | ||
6 |
class Migration(migrations.Migration): |
|
7 | ||
8 |
initial = True |
|
9 | ||
10 |
dependencies = [ |
|
11 |
('base', '0029_auto_20210202_1627'), |
|
12 |
] |
|
13 | ||
14 |
operations = [ |
|
15 |
migrations.CreateModel( |
|
16 |
name='SignalArretes', |
|
17 |
fields=[ |
|
18 |
( |
|
19 |
'id', |
|
20 |
models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID'), |
|
21 |
), |
|
22 |
('title', models.CharField(max_length=50, verbose_name='Title')), |
|
23 |
('slug', models.SlugField(unique=True, verbose_name='Identifier')), |
|
24 |
('description', models.TextField(verbose_name='Description')), |
|
25 |
('base_url', models.URLField(verbose_name='Base API URL')), |
|
26 |
( |
|
27 |
'users', |
|
28 |
models.ManyToManyField( |
|
29 |
blank=True, |
|
30 |
related_name='_signalarretes_users_+', |
|
31 |
related_query_name='+', |
|
32 |
to='base.ApiUser', |
|
33 |
), |
|
34 |
), |
|
35 |
], |
|
36 |
options={ |
|
37 |
'verbose_name': 'Signal Arrêtés', |
|
38 |
}, |
|
39 |
), |
|
40 |
] |
passerelle/apps/signal_arretes/models.py | ||
---|---|---|
1 |
# -*- coding: utf-8 -*- |
|
2 |
# Copyright (C) 2020 Entr'ouvert |
|
3 |
# |
|
4 |
# This program is free software: you can redistribute it and/or modify it |
|
5 |
# under the terms of the GNU Affero General Public License as published |
|
6 |
# by the Free Software Foundation, either version 3 of the License, or |
|
7 |
# (at your option) any later version. |
|
8 |
# |
|
9 |
# This program is distributed in the hope that it will be useful, |
|
10 |
# but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
11 |
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
12 |
# GNU Affero General Public License for more details. |
|
13 |
# |
|
14 |
# You should have received a copy of the GNU Affero General Public License |
|
15 |
# along with this program. If not, see <http://www.gnu.org/licenses/>. |
|
16 | ||
17 | ||
18 |
import binascii |
|
19 |
import json |
|
20 |
import re |
|
21 |
from base64 import b64decode |
|
22 |
from datetime import datetime |
|
23 | ||
24 |
from django.db import models |
|
25 |
from django.http import HttpResponse |
|
26 |
from django.utils.text import slugify |
|
27 |
from django.utils.translation import ugettext_lazy as _ |
|
28 |
from requests import RequestException |
|
29 | ||
30 |
from passerelle.base.models import BaseResource |
|
31 |
from passerelle.utils.api import endpoint |
|
32 |
from passerelle.utils.jsonresponse import APIError |
|
33 | ||
34 |
REQUEST_SCHEMA = { |
|
35 |
'type': 'object', |
|
36 |
'$schema': 'http://json-schema.org/draft-04/schema#', |
|
37 |
'title': 'Signal Arretes', |
|
38 |
'description': 'Public Occupation Request Schema', |
|
39 |
'required': [ |
|
40 |
'declarant_organisation', |
|
41 |
'declarant_quality', |
|
42 |
'declarant_civility', |
|
43 |
'declarant_name', |
|
44 |
'declarant_surname', |
|
45 |
'declarant_email', |
|
46 |
'occupation_lane', |
|
47 |
'occupation_city', |
|
48 |
'occupation_type', |
|
49 |
'occupation_start_date', |
|
50 |
'occupation_end_date', |
|
51 |
], |
|
52 |
'properties': { |
|
53 |
'declarant_organisation': { |
|
54 |
'description': _('"Individual" or enterprise name'), |
|
55 |
'type': 'string', |
|
56 |
}, |
|
57 |
'declarant_siret': { |
|
58 |
'description': _('Entreprise SIRET number'), |
|
59 |
'type': ['string', 'null'], |
|
60 |
'pattern': '\\d{14}', |
|
61 |
'pattern_description': _('14-digits siret number'), |
|
62 |
}, |
|
63 |
'declarant_quality': { |
|
64 |
'description': _('Declarant quality'), |
|
65 |
'type': 'string', |
|
66 |
'enum': ['Particulier', 'Entreprise', 'Association'], |
|
67 |
}, |
|
68 |
'file_number': {'description': _('Declarant reference'), 'type': ['string', 'null']}, |
|
69 |
'declarant_civility': { |
|
70 |
'description': _('Declarant civility'), |
|
71 |
'type': 'string', |
|
72 |
'enum': ['MONSIEUR', 'MADAME'], |
|
73 |
}, |
|
74 |
'declarant_name': {'description': _('Declarant name'), 'type': 'string'}, |
|
75 |
'declarant_surname': {'description': _('Declarant surname'), 'type': 'string'}, |
|
76 |
'declarant_address': {'description': _('Declarant address'), 'type': ['string', 'null']}, |
|
77 |
'declarant_zip': {'description': _('Declarant ZIP code'), 'type': ['string', 'null']}, |
|
78 |
'declarant_city': {'description': _('Declarant city'), 'type': ['string', 'null']}, |
|
79 |
'declarant_email': {'description': _('Declarant email address'), 'type': 'string'}, |
|
80 |
'declarant_phone': {'description': _('Declarant phone number'), 'type': ['string', 'null']}, |
|
81 |
'occupation_lane': {'description': _('Occupation lane'), 'type': 'string'}, |
|
82 |
'occupation_number': {'description': _('Occupation lane number'), 'type': ['string', 'null']}, |
|
83 |
'occupation_city': {'description': _('Occupation city'), 'type': 'string'}, |
|
84 |
'occupation_type': {'description': _('Occupation type'), 'type': 'string'}, |
|
85 |
'occupation_start_date': { |
|
86 |
'description': _('Occupation start date'), |
|
87 |
'type': 'string', |
|
88 |
'format': 'date', |
|
89 |
}, |
|
90 |
'occupation_end_date': {'description': _('Occupation end date'), 'type': 'string', 'format': 'date'}, |
|
91 |
}, |
|
92 |
} |
|
93 | ||
94 | ||
95 |
class SignalArretes(BaseResource): |
|
96 |
base_url = models.URLField(_('Base API URL')) |
|
97 |
category = _('Business Process Connectors') |
|
98 | ||
99 |
class Meta: |
|
100 |
verbose_name = 'Signal Arrêtés' |
|
101 | ||
102 |
@classmethod |
|
103 |
def get_verbose_name(cls): |
|
104 |
return cls._meta.verbose_name |
|
105 | ||
106 |
@endpoint( |
|
107 |
description=_('Create a public domain occupation request'), |
|
108 |
perm='can_access', |
|
109 |
post={'request_body': {'schema': {'application/json': REQUEST_SCHEMA}}}, |
|
110 |
) |
|
111 |
def create_request(self, request, post_data): |
|
112 |
def _format_date(date_string): |
|
113 |
return datetime.strptime(date_string, '%d/%m/%Y').strftime('%Y-%m-%d') |
|
114 | ||
115 |
query_data = { |
|
116 |
'organisationDeclarante': post_data['declarant_organisation'], |
|
117 |
'qualite': post_data['declarant_quality'], |
|
118 |
'contact': { |
|
119 |
'civilite': post_data['declarant_civility'], |
|
120 |
'nom': post_data['declarant_name'], |
|
121 |
'prenom': post_data['declarant_surname'], |
|
122 |
'email': post_data['declarant_email'], |
|
123 |
}, |
|
124 |
'localisation': { |
|
125 |
'nomVoie': post_data['occupation_lane'], |
|
126 |
'commune': post_data['occupation_city'], |
|
127 |
'natureOccupation': post_data['occupation_type'], |
|
128 |
'dateDebut': _format_date(post_data['occupation_start_date']), |
|
129 |
'dateFin': _format_date(post_data['occupation_end_date']), |
|
130 |
}, |
|
131 |
} |
|
132 | ||
133 |
def _update_data(key, target, target_key): |
|
134 |
if key in post_data and post_data[key]: |
|
135 |
target[target_key] = post_data[key] |
|
136 | ||
137 |
_update_data('declarant_siret', query_data, 'SIRET') |
|
138 |
_update_data('file_number', query_data, 'numeroDossier') |
|
139 | ||
140 |
_update_data('declarant_address', query_data['contact'], 'adresseLigne1') |
|
141 |
_update_data('declarant_zip', query_data['contact'], 'CP') |
|
142 |
_update_data('declarant_city', query_data['contact'], 'ville') |
|
143 |
_update_data('declarant_phone', query_data['contact'], 'telephone') |
|
144 |
_update_data('occupation_number', query_data['localisation'], 'numeroVoie') |
|
145 | ||
146 |
result_string = self._call('CreationDODP', query_data) |
|
147 |
if not isinstance(result_string, str): |
|
148 |
raise APIError('Expected a string') |
|
149 | ||
150 |
try: |
|
151 |
result = json.loads(result_string) |
|
152 |
except ValueError: |
|
153 |
raise APIError('Returned string should be valid json') |
|
154 | ||
155 |
if not isinstance(result, dict) or len(result) != 1: |
|
156 |
raise APIError('Expected a dictionary with one element') |
|
157 | ||
158 |
return {'request_id': list(result.keys())[0]} |
|
159 | ||
160 |
@endpoint( |
|
161 |
description=_('Get cities available in Signal Arrêtés'), |
|
162 |
perm='can_access', |
|
163 |
) |
|
164 |
def get_cities(self, request, **kwargs): |
|
165 |
return self._get_list('GetCommunes') |
|
166 | ||
167 |
@endpoint( |
|
168 |
description=_('Get lanes available in Signal Arrêtés'), |
|
169 |
perm='can_access', |
|
170 |
parameters={ |
|
171 |
'city': {'description': _('Get lanes for this city')}, |
|
172 |
}, |
|
173 |
) |
|
174 |
def get_lanes(self, request, city): |
|
175 |
return self._get_list('GetVoies', {'Commune': city}) |
|
176 | ||
177 |
@endpoint( |
|
178 |
description=_('Get available occupation types in Signal Arrêtés'), |
|
179 |
perm='can_access', |
|
180 |
) |
|
181 |
def get_occupation_types(self, request): |
|
182 |
return self._get_list('GetNaturesOccupation') |
|
183 | ||
184 |
@endpoint( |
|
185 |
description=_('Get status of given request in signal arrêtés'), |
|
186 |
perm='can_access', |
|
187 |
parameters={ |
|
188 |
'request_id': {'description': _('The occupation request id returned by create_request')}, |
|
189 |
}, |
|
190 |
) |
|
191 |
def get_request_status(self, request, request_id): |
|
192 |
return {'request_status': self._get_value('GetStatutDemande', request_id=request_id)} |
|
193 | ||
194 |
@endpoint( |
|
195 |
description=_('Get document associated with given request in signal arrêtés'), |
|
196 |
perm='can_access', |
|
197 |
parameters={ |
|
198 |
'request_id': {'description': _('The occupation request id returned by create_request')}, |
|
199 |
}, |
|
200 |
) |
|
201 |
def get_request_document(self, request, request_id): |
|
202 |
result = self._get_value('GetDocumentDemande', request_id=request_id) |
|
203 | ||
204 |
filename = result['name'] |
|
205 |
content_type = result['contentType'] |
|
206 | ||
207 |
try: |
|
208 |
content = b64decode(result['content'], validate=True) |
|
209 |
except binascii.Error as e: |
|
210 |
raise APIError(f'Corrupted base64 content {e}') |
|
211 | ||
212 |
response = HttpResponse(content, content_type=content_type) |
|
213 |
response['Content-Disposition'] = f'attachment; filename="{filename}"' |
|
214 | ||
215 |
return response |
|
216 | ||
217 |
def _call(self, endpoint, post_data=None): |
|
218 |
url = f'{self.base_url}/CreationDemandeService.svc/{endpoint}' |
|
219 |
if not post_data: |
|
220 |
response = self.requests.get(url) |
|
221 |
else: |
|
222 |
response = self.requests.post(url, json=post_data) |
|
223 | ||
224 |
try: |
|
225 |
response.raise_for_status() |
|
226 |
except RequestException as e: |
|
227 |
if response.status_code == 400: |
|
228 |
error_msg_match = re.search( |
|
229 |
'Le message d\'exception est \'(.*)\'\\. Pour plus d\'informations', response.text |
|
230 |
) |
|
231 |
if error_msg_match: |
|
232 |
error_message = error_msg_match.group(1) |
|
233 |
raise APIError( |
|
234 |
'An error occured during the request to Signal Arrêtés: %s' % error_message |
|
235 |
) |
|
236 | ||
237 |
raise APIError('An error occured during the request to Signal Arrêtés: %s' % e) |
|
238 | ||
239 |
try: |
|
240 |
return response.json() |
|
241 |
except ValueError: |
|
242 |
raise APIError('Expected valid json') |
|
243 | ||
244 |
def _get_value(self, endpoint, post_data=None, request_id=None): |
|
245 |
if request_id: |
|
246 |
url = f"{endpoint}/{request_id}" |
|
247 |
else: |
|
248 |
url = endpoint |
|
249 | ||
250 |
response = self._call(url, post_data=post_data) |
|
251 |
result_key = f'{endpoint}Result' |
|
252 |
if not isinstance(response, dict) or result_key not in response: |
|
253 |
raise APIError('Expected a dictionary with a %s key' % result_key) |
|
254 | ||
255 |
result_str = response[result_key] |
|
256 |
try: |
|
257 |
return json.loads(result_str) |
|
258 |
except ValueError: |
|
259 |
raise APIError('Expected valid json string at %s key' % result_key) |
|
260 | ||
261 |
def _get_list(self, endpoint, post_data=None): |
|
262 |
result = self._get_value(endpoint, post_data=post_data) |
|
263 |
if not isinstance(result, list): |
|
264 |
raise APIError('Expected a list') |
|
265 | ||
266 |
return {'data': [{'id': slugify(it), 'text': it} for it in result]} |
passerelle/settings.py | ||
---|---|---|
163 | 163 |
'passerelle.apps.plone_restapi', |
164 | 164 |
'passerelle.apps.sector', |
165 | 165 |
'passerelle.apps.sfr_dmc', |
166 |
'passerelle.apps.signal_arretes', |
|
166 | 167 |
'passerelle.apps.soap', |
167 | 168 |
'passerelle.apps.solis', |
168 | 169 |
'passerelle.apps.sp_fr', |
tests/test_signal_arretes.py | ||
---|---|---|
1 |
# passerelle - uniform access to multiple data sources and services |
|
2 |
# Copyright (C) 2022 Entr'ouvert |
|
3 |
# |
|
4 |
# This program is free software: you can redistribute it and/or modify it |
|
5 |
# under the terms of the GNU Affero General Public License as published |
|
6 |
# by the Free Software Foundation, either version 3 of the License, or |
|
7 |
# (at your option) any later version. |
|
8 |
# |
|
9 |
# This program is distributed in the hope that it will be useful, |
|
10 |
# but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
11 |
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
12 |
# GNU Affero General Public License for more details. |
|
13 |
# |
|
14 |
# You should have received a copy of the GNU Affero General Public License |
|
15 |
# along with this program. If not, see <http://www.gnu.org/licenses/>. |
|
16 | ||
17 |
import json |
|
18 |
from base64 import standard_b64encode |
|
19 | ||
20 |
import httmock |
|
21 |
import pytest |
|
22 | ||
23 |
from passerelle.apps.signal_arretes.models import SignalArretes |
|
24 |
from tests.utils import generic_endpoint_url, setup_access_rights |
|
25 | ||
26 | ||
27 |
@pytest.fixture() |
|
28 |
def connector(db): |
|
29 |
return setup_access_rights( |
|
30 |
SignalArretes.objects.create(slug='test', base_url='http://test.signalarretes.net') |
|
31 |
) |
|
32 | ||
33 | ||
34 |
REQUIRED_PARAMETERS = { |
|
35 |
'declarant_organisation': 'ACME', |
|
36 |
'declarant_siret': None, |
|
37 |
'declarant_quality': 'Entreprise', |
|
38 |
'file_number': None, |
|
39 |
'declarant_civility': 'MADAME', |
|
40 |
'declarant_name': 'John', |
|
41 |
'declarant_surname': 'Doe', |
|
42 |
'declarant_address': None, |
|
43 |
'declarant_zip': None, |
|
44 |
'declarant_city': None, |
|
45 |
'declarant_email': 'john@doe.net', |
|
46 |
'declarant_phone': None, |
|
47 |
'occupation_lane': 'Sesame Street', |
|
48 |
'occupation_number': '10', |
|
49 |
'occupation_city': 'Melun', |
|
50 |
'occupation_type': 'Base de vie', |
|
51 |
'occupation_start_date': '02/06/2022', |
|
52 |
'occupation_end_date': '03/06/2022', |
|
53 |
} |
|
54 | ||
55 |
ALL_PARAMETERS = dict(REQUIRED_PARAMETERS) |
|
56 |
ALL_PARAMETERS.update( |
|
57 |
{ |
|
58 |
'declarant_siret': '00000000000000', |
|
59 |
'file_number': 'reference_dossier', |
|
60 |
'declarant_address': '6 Proudhon street', |
|
61 |
'declarant_zip': '42 42420', |
|
62 |
'declarant_city': 'Melun', |
|
63 |
'declarant_phone': '0636656565', |
|
64 |
'occupation_lane': 'Sesame Street', |
|
65 |
} |
|
66 |
) |
|
67 | ||
68 | ||
69 |
def _check_required_parameters(data): |
|
70 |
assert data['organisationDeclarante'] == 'ACME' |
|
71 |
assert data['qualite'] == 'Entreprise' |
|
72 |
assert data['contact']['civilite'] == 'MADAME' |
|
73 |
assert data['contact']['nom'] == 'John' |
|
74 |
assert data['contact']['prenom'] == 'Doe' |
|
75 |
assert data['contact']['email'] == 'john@doe.net' |
|
76 |
assert data['localisation']['nomVoie'] == 'Sesame Street' |
|
77 |
assert data['localisation']['commune'] == 'Melun' |
|
78 |
assert data['localisation']['natureOccupation'] == 'Base de vie' |
|
79 |
assert data['localisation']['dateDebut'] == '2022-06-02' |
|
80 |
assert data['localisation']['dateFin'] == '2022-06-03' |
|
81 |
assert data['localisation']['numeroVoie'] == '10' |
|
82 | ||
83 | ||
84 |
def test_create_request(app, connector): |
|
85 |
@httmock.all_requests |
|
86 |
def create_request_mock(url, request): |
|
87 |
assert request.path_url == '/CreationDemandeService.svc/CreationDODP' |
|
88 |
data = json.loads(request.body) |
|
89 | ||
90 |
_check_required_parameters(data) |
|
91 | ||
92 |
assert data['SIRET'] == '00000000000000' |
|
93 |
assert data['numeroDossier'] == 'reference_dossier' |
|
94 |
assert data['contact']['adresseLigne1'] == '6 Proudhon street' |
|
95 |
assert data['contact']['CP'] == '42 42420' |
|
96 |
assert data['contact']['ville'] == 'Melun' |
|
97 |
assert data['contact']['telephone'] == '0636656565' |
|
98 | ||
99 |
result_json = json.dumps({'D0000_DOT': 'Enregistré'}) |
|
100 |
return httmock.response(200, json.dumps(result_json)) |
|
101 | ||
102 |
endpoint = generic_endpoint_url('signal-arretes', 'create_request', slug=connector.slug) |
|
103 | ||
104 |
with httmock.HTTMock(create_request_mock): |
|
105 |
response = app.post_json(endpoint, params=ALL_PARAMETERS) |
|
106 | ||
107 |
result = response.json |
|
108 |
assert result['request_id'] == 'D0000_DOT' |
|
109 | ||
110 | ||
111 |
def test_create_request_optional_parameters(app, connector): |
|
112 |
@httmock.all_requests |
|
113 |
def create_request_mock(url, request): |
|
114 |
data = json.loads(request.body) |
|
115 | ||
116 |
_check_required_parameters(data) |
|
117 | ||
118 |
assert 'SIRET' not in data |
|
119 |
assert 'numeroDossier' not in data |
|
120 |
assert 'adresseLigne1' not in data['contact'] |
|
121 |
assert 'CP' not in data['contact'] |
|
122 |
assert 'ville' not in data['contact'] |
|
123 |
assert 'telephone' not in data['contact'] |
|
124 | ||
125 |
result_json = json.dumps({'D0000_DOT': 'Enregistré'}) |
|
126 |
return httmock.response(200, json.dumps(result_json)) |
|
127 | ||
128 |
endpoint = generic_endpoint_url('signal-arretes', 'create_request', slug=connector.slug) |
|
129 | ||
130 |
with httmock.HTTMock(create_request_mock): |
|
131 |
response = app.post_json( |
|
132 |
endpoint, |
|
133 |
params=REQUIRED_PARAMETERS, |
|
134 |
) |
|
135 | ||
136 |
result = response.json |
|
137 |
assert result['request_id'] == 'D0000_DOT' |
|
138 | ||
139 | ||
140 |
def test_create_request_errors(app, connector): |
|
141 |
def _test_error(response, error_message): |
|
142 |
@httmock.all_requests |
|
143 |
def bad_json_response(url, request): |
|
144 |
return httmock.response(200, response) |
|
145 | ||
146 |
endpoint = generic_endpoint_url('signal-arretes', 'create_request', slug=connector.slug) |
|
147 | ||
148 |
with httmock.HTTMock(bad_json_response): |
|
149 |
response = app.post_json(endpoint, params=REQUIRED_PARAMETERS) |
|
150 | ||
151 |
json_response = response.json |
|
152 |
assert 'err' in json_response |
|
153 |
assert json_response['err'] |
|
154 |
assert error_message in json_response['err_desc'] |
|
155 | ||
156 |
_test_error('Not valid json', 'Expected valid json') |
|
157 |
_test_error('[]', 'Expected a string') |
|
158 |
_test_error('"Invalid json"', 'Returned string should be valid json') |
|
159 |
_test_error('"[]"', 'Expected a dictionary with one element') |
|
160 |
_test_error('"{}"', 'Expected a dictionary with one element') |
|
161 | ||
162 | ||
163 |
def test_get_cities(app, connector): |
|
164 |
@httmock.all_requests |
|
165 |
def get_cities_mock(url, request): |
|
166 |
assert request.path_url == '/CreationDemandeService.svc/GetCommunes' |
|
167 |
result_json = json.dumps(["Clapotis Les Canards", "Grosboule Les Bains"]) |
|
168 |
return httmock.response(200, json.dumps({'GetCommunesResult': result_json})) |
|
169 | ||
170 |
endpoint = generic_endpoint_url('signal-arretes', 'get_cities', slug=connector.slug) |
|
171 | ||
172 |
with httmock.HTTMock(get_cities_mock): |
|
173 |
response = app.get(endpoint) |
|
174 | ||
175 |
assert response.json['data'] == [ |
|
176 |
{'id': 'clapotis-les-canards', 'text': 'Clapotis Les Canards'}, |
|
177 |
{'id': 'grosboule-les-bains', 'text': 'Grosboule Les Bains'}, |
|
178 |
] |
|
179 | ||
180 | ||
181 |
def test_get_lanes(app, connector): |
|
182 |
@httmock.all_requests |
|
183 |
def get_lanes_mock(url, request): |
|
184 |
assert request.path_url == '/CreationDemandeService.svc/GetVoies' |
|
185 |
data = json.loads(request.body) |
|
186 |
assert data == {'Commune': 'Clapotis Les Canards'} |
|
187 |
result_json = json.dumps(["Rue Sacco", "Rue Vanzetti"]) |
|
188 |
return httmock.response(200, json.dumps({'GetVoiesResult': result_json})) |
|
189 | ||
190 |
endpoint = generic_endpoint_url('signal-arretes', 'get_lanes', slug=connector.slug) |
|
191 | ||
192 |
with httmock.HTTMock(get_lanes_mock): |
|
193 |
response = app.get(endpoint, params={'city': 'Clapotis Les Canards'}) |
|
194 | ||
195 |
assert response.json['data'] == [ |
|
196 |
{'id': 'rue-sacco', 'text': 'Rue Sacco'}, |
|
197 |
{'id': 'rue-vanzetti', 'text': 'Rue Vanzetti'}, |
|
198 |
] |
|
199 | ||
200 | ||
201 |
def test_get_occupation_types(app, connector): |
|
202 |
@httmock.all_requests |
|
203 |
def get_occupation_types_mock(url, request): |
|
204 |
assert request.path_url == '/CreationDemandeService.svc/GetNaturesOccupation' |
|
205 |
result_json = json.dumps(["Déménagement", "Festival"]) |
|
206 |
return httmock.response(200, json.dumps({'GetNaturesOccupationResult': result_json})) |
|
207 | ||
208 |
endpoint = generic_endpoint_url('signal-arretes', 'get_occupation_types', slug=connector.slug) |
|
209 | ||
210 |
with httmock.HTTMock(get_occupation_types_mock): |
|
211 |
response = app.get(endpoint) |
|
212 | ||
213 |
assert response.json['data'] == [ |
|
214 |
{'id': 'demenagement', 'text': 'Déménagement'}, |
|
215 |
{'id': 'festival', 'text': 'Festival'}, |
|
216 |
] |
|
217 | ||
218 | ||
219 |
def test_get_request_status(app, connector): |
|
220 |
@httmock.all_requests |
|
221 |
def get_occupation_types_mock(url, request): |
|
222 |
assert request.path_url == '/CreationDemandeService.svc/GetStatutDemande/test_request_id' |
|
223 |
result_json = json.dumps("Enregistré") |
|
224 |
return httmock.response(200, json.dumps({'GetStatutDemandeResult': result_json})) |
|
225 | ||
226 |
endpoint = generic_endpoint_url('signal-arretes', 'get_request_status', slug=connector.slug) |
|
227 | ||
228 |
with httmock.HTTMock(get_occupation_types_mock): |
|
229 |
response = app.get(endpoint, params={'request_id': 'test_request_id'}) |
|
230 | ||
231 |
assert not response.json['err'] |
|
232 |
assert response.json['request_status'] == 'Enregistré' |
|
233 | ||
234 | ||
235 |
def test_get_request_document(app, connector): |
|
236 |
test_content = 'Test file content'.encode('utf-8') |
|
237 | ||
238 |
@httmock.all_requests |
|
239 |
def get_document_mock(url, request): |
|
240 |
assert request.path_url == '/CreationDemandeService.svc/GetDocumentDemande/test_request_id' |
|
241 |
content = standard_b64encode(test_content).decode('utf-8') |
|
242 |
result_json = json.dumps( |
|
243 |
{'contentType': 'text/test-data', 'name': 'test_filename', 'content': content} |
|
244 |
) |
|
245 |
return httmock.response(200, json.dumps({'GetDocumentDemandeResult': result_json})) |
|
246 | ||
247 |
endpoint = generic_endpoint_url('signal-arretes', 'get_request_document', slug=connector.slug) |
|
248 | ||
249 |
with httmock.HTTMock(get_document_mock): |
|
250 |
response = app.get(endpoint, params={'request_id': 'test_request_id'}) |
|
251 | ||
252 |
assert response.headers['Content-Type'] == 'text/test-data' |
|
253 |
assert response.headers['Content-Disposition'] == 'attachment; filename="test_filename"' |
|
254 |
assert response.body == test_content |
|
255 | ||
256 |
@httmock.all_requests |
|
257 |
def corrupted_content_mock(url, request): |
|
258 |
result_json = json.dumps( |
|
259 |
{'contentType': 'text/test-data', 'name': 'test_filename', 'content': '$$$$$$$'} |
|
260 |
) |
|
261 |
return httmock.response(200, json.dumps({'GetDocumentDemandeResult': result_json})) |
|
262 | ||
263 |
with httmock.HTTMock(corrupted_content_mock): |
|
264 |
response = app.get(endpoint, params={'request_id': 'test_request_id'}) |
|
265 | ||
266 |
json_response = response.json |
|
267 | ||
268 |
assert 'err' in json_response |
|
269 |
assert json_response['err'] |
|
270 |
assert 'Corrupted base64 content' in json_response['err_desc'] |
|
271 | ||
272 | ||
273 |
def test_error_handling(app, connector): |
|
274 |
def _test_error(status_code, body, expected_message): |
|
275 |
@httmock.all_requests |
|
276 |
def error_mock(url, request): |
|
277 |
return httmock.response(status_code, body) |
|
278 | ||
279 |
endpoint = generic_endpoint_url('signal-arretes', 'get_cities', slug=connector.slug) |
|
280 |
with httmock.HTTMock(error_mock): |
|
281 |
response = app.get(endpoint) |
|
282 | ||
283 |
json_response = response.json |
|
284 |
assert 'err' in json_response |
|
285 |
assert json_response['err'] |
|
286 |
assert expected_message in json_response['err_desc'] |
|
287 | ||
288 |
_test_error( |
|
289 |
400, |
|
290 |
'Le message d\'exception est \'Test error message\'. Pour plus d\'informations thanks a lot to use HTML as return of a json api.', |
|
291 |
'An error occured during the request to Signal Arrêtés: Test error message', |
|
292 |
) |
|
293 | ||
294 |
_test_error(500, 'Unmatched message', 'An error occured during the request to Signal Arrêtés') |
|
295 |
_test_error(200, 'Invalid json', 'Expected valid json') |
|
296 |
_test_error(200, '[]', 'Expected a dictionary with a GetCommunesResult key') |
|
297 |
_test_error(200, '{}', 'Expected a dictionary with a GetCommunesResult key') |
|
298 |
_test_error( |
|
299 |
200, '{"GetCommunesResult": "Invalid json"}', 'Expected valid json string at GetCommunesResult key' |
|
300 |
) |
|
301 |
_test_error(200, '{"GetCommunesResult": "{}"}', 'Expected a list') |
|
0 |
- |