0001-esirius-add-e-sirius-connector-51365.patch
passerelle/apps/esirius/migrations/0001_initial.py | ||
---|---|---|
1 |
# -*- coding: utf-8 -*- |
|
2 |
# Generated by Django 1.11.18 on 2021-03-01 14:52 |
|
3 |
from __future__ import unicode_literals |
|
4 | ||
5 |
from django.db import migrations, models |
|
6 |
import passerelle.apps.esirius.models |
|
7 | ||
8 | ||
9 |
class Migration(migrations.Migration): |
|
10 | ||
11 |
initial = True |
|
12 | ||
13 |
dependencies = [ |
|
14 |
('base', '0029_auto_20210202_1627'), |
|
15 |
] |
|
16 | ||
17 |
operations = [ |
|
18 |
migrations.CreateModel( |
|
19 |
name='ESirius', |
|
20 |
fields=[ |
|
21 |
( |
|
22 |
'id', |
|
23 |
models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID'), |
|
24 |
), |
|
25 |
('title', models.CharField(max_length=50, verbose_name='Title')), |
|
26 |
('slug', models.SlugField(unique=True, verbose_name='Identifier')), |
|
27 |
('description', models.TextField(verbose_name='Description')), |
|
28 |
( |
|
29 |
'basic_auth_username', |
|
30 |
models.CharField( |
|
31 |
blank=True, max_length=128, verbose_name='Basic authentication username' |
|
32 |
), |
|
33 |
), |
|
34 |
( |
|
35 |
'basic_auth_password', |
|
36 |
models.CharField( |
|
37 |
blank=True, max_length=128, verbose_name='Basic authentication password' |
|
38 |
), |
|
39 |
), |
|
40 |
( |
|
41 |
'client_certificate', |
|
42 |
models.FileField( |
|
43 |
blank=True, null=True, upload_to='', verbose_name='TLS client certificate' |
|
44 |
), |
|
45 |
), |
|
46 |
( |
|
47 |
'trusted_certificate_authorities', |
|
48 |
models.FileField(blank=True, null=True, upload_to='', verbose_name='TLS trusted CAs'), |
|
49 |
), |
|
50 |
('verify_cert', models.BooleanField(default=True, verbose_name='TLS verify certificates')), |
|
51 |
( |
|
52 |
'http_proxy', |
|
53 |
models.CharField(blank=True, max_length=128, verbose_name='HTTP and HTTPS proxy'), |
|
54 |
), |
|
55 |
( |
|
56 |
'secret_id', |
|
57 |
models.CharField(blank=True, max_length=128, verbose_name='Application identifier'), |
|
58 |
), |
|
59 |
( |
|
60 |
'secret_key', |
|
61 |
passerelle.apps.esirius.models.DESKeyModel( |
|
62 |
blank=True, max_length=128, verbose_name='Secret Key' |
|
63 |
), |
|
64 |
), |
|
65 |
( |
|
66 |
'base_url', |
|
67 |
models.CharField( |
|
68 |
help_text='example: https://HOST/ePlanning/webservices/api/', |
|
69 |
max_length=256, |
|
70 |
verbose_name='ePlanning webservices URL', |
|
71 |
), |
|
72 |
), |
|
73 |
( |
|
74 |
'users', |
|
75 |
models.ManyToManyField( |
|
76 |
blank=True, related_name='_esirius_users_+', related_query_name='+', to='base.ApiUser' |
|
77 |
), |
|
78 |
), |
|
79 |
], |
|
80 |
options={ |
|
81 |
'verbose_name': 'eSirius', |
|
82 |
}, |
|
83 |
), |
|
84 |
] |
passerelle/apps/esirius/models.py | ||
---|---|---|
1 |
# passerelle - uniform access to multiple data sources and services |
|
2 |
# Copyright (C) 2021 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 base64 |
|
19 |
from time import time |
|
20 |
from urllib.parse import urljoin |
|
21 | ||
22 |
from Cryptodome.Cipher import DES |
|
23 |
from Cryptodome.Util.Padding import pad |
|
24 | ||
25 |
from django.db import models |
|
26 |
from django.core.exceptions import ValidationError |
|
27 |
from django.utils.encoding import force_bytes |
|
28 |
from django.utils.translation import ugettext_lazy as _ |
|
29 | ||
30 |
from passerelle.base.models import BaseResource, HTTPResource |
|
31 |
from passerelle.utils.api import endpoint |
|
32 |
from passerelle.utils.jsonresponse import APIError |
|
33 | ||
34 | ||
35 |
CREATE_APPOINTMENT_SCHEMA = { |
|
36 |
'$schema': 'http://json-schema.org/draft-04/schema#', |
|
37 |
"type": "object", |
|
38 |
'properties': { |
|
39 |
'idSys': {'type': 'string', 'pattern': '^[0-9]*$'}, |
|
40 |
'CodeRDV': {'type': 'string'}, |
|
41 |
'beginDate': {'type': 'string', 'pattern': '^[0-9]{4}-[0-9]{2}-[0-9]{2}$'}, |
|
42 |
'beginTime': {'type': 'string', 'pattern': '^[0-9]{2}:[0-9]{2}$'}, |
|
43 |
'endDate': {'type': 'string', 'pattern': '^[0-9]{4}-[0-9]{2}-[0-9]{2}$'}, |
|
44 |
'endTime': {'type': 'string', 'pattern': '^[0-9]{2}:[0-9]{2}$'}, |
|
45 |
'comment': {'type': 'string'}, |
|
46 |
'isoLanguage': {'description': 'ex: fr', 'type': 'string'}, |
|
47 |
'needsConfirmation': {'description': 'boolean expected', 'type': 'string'}, |
|
48 |
'rdvChannel': {'description': 'ex: EAPP0', 'type': 'string'}, |
|
49 |
'receptionChannel': {'type': 'string'}, |
|
50 |
'owner': {'type': 'object', 'properties': {'key': {'type': 'string'}, 'value': {'type': 'string'}}}, |
|
51 |
'user': { |
|
52 |
'type': 'object', |
|
53 |
'properties': { |
|
54 |
'idSys': {'type': 'string', 'pattern': '^[0-9]*$'}, |
|
55 |
'personalIdentity': {'type': 'string'}, |
|
56 |
'additionalPersonalIdentity': {"type": "array", "items": {'type': 'string'}}, |
|
57 |
'lastName': {'type': 'string'}, |
|
58 |
'civility': {'type': 'string'}, |
|
59 |
'firstName': {'type': 'string'}, |
|
60 |
'birthday': {'type': 'string'}, |
|
61 |
'email': {'type': 'string'}, |
|
62 |
'fixPhone': {'type': 'string'}, |
|
63 |
'phone': {'type': 'string'}, |
|
64 |
'address': { |
|
65 |
'type': 'object', |
|
66 |
'properties': { |
|
67 |
'line1': {'type': 'string'}, |
|
68 |
'line2': {'type': 'string'}, |
|
69 |
'zipCode': {'type': 'string'}, |
|
70 |
'city': {'type': 'string'}, |
|
71 |
'country': {'type': 'string'}, |
|
72 |
}, |
|
73 |
}, |
|
74 |
}, |
|
75 |
}, |
|
76 |
'serviceId': {'type': 'string'}, |
|
77 |
'siteCode': {'type': 'string'}, |
|
78 |
"resources": { |
|
79 |
'type': 'object', |
|
80 |
'properties': { |
|
81 |
'id': {'type': 'string', 'pattern': '^[0-9]*$'}, |
|
82 |
'key': {'type': 'string'}, |
|
83 |
'type': {'type': 'string'}, |
|
84 |
'name': {'type': 'string'}, |
|
85 |
'station': { |
|
86 |
'type': 'object', |
|
87 |
'properties': { |
|
88 |
'id': {'type': 'string', 'pattern': '^[0-9]*$'}, |
|
89 |
'key': {'type': 'string'}, |
|
90 |
'name': {'type': 'string'}, |
|
91 |
}, |
|
92 |
}, |
|
93 |
}, |
|
94 |
}, |
|
95 |
'motives': { |
|
96 |
"type": "array", |
|
97 |
"items": { |
|
98 |
'type': 'object', |
|
99 |
'properties': { |
|
100 |
'id': {'type': 'string', 'pattern': '^[0-9]*$'}, |
|
101 |
'name': {'type': 'string'}, |
|
102 |
'shortName': {'type': 'string'}, |
|
103 |
'processingTime': {'type': 'string', 'pattern': '^[0-9]*$'}, |
|
104 |
'externalModuleAccess': {'type': 'string', 'pattern': '^[0-9]*$'}, |
|
105 |
'quantity': {'type': 'string', 'pattern': '^[0-9]*$'}, |
|
106 |
'usePremotiveQuantity': {'description': 'boolean expected', 'type': 'string'}, |
|
107 |
}, |
|
108 |
}, |
|
109 |
}, |
|
110 |
}, |
|
111 |
'unflatten': True, |
|
112 |
} |
|
113 | ||
114 | ||
115 |
class DESKeyModel(models.CharField): |
|
116 |
def clean(self, value, model_instance): |
|
117 |
if len(value) < 8: |
|
118 |
raise ValidationError(_('DES key must be 8 bytes long (longer keys are truncated)')) |
|
119 |
return super().clean(value, model_instance) |
|
120 | ||
121 | ||
122 |
class ESirius(BaseResource, HTTPResource): |
|
123 |
secret_id = models.CharField(max_length=128, verbose_name=_('Application identifier'), blank=True) |
|
124 |
secret_key = DESKeyModel(max_length=128, verbose_name=_('Secret Key'), blank=True) |
|
125 |
base_url = models.CharField( |
|
126 |
max_length=256, |
|
127 |
blank=False, |
|
128 |
verbose_name=_('ePlanning webservices URL'), |
|
129 |
help_text=_('example: https://HOST/ePlanning/webservices/api/'), |
|
130 |
) |
|
131 | ||
132 |
category = _('Business Process Connectors') |
|
133 | ||
134 |
class Meta: |
|
135 |
verbose_name = _('eSirius') |
|
136 | ||
137 |
def request(self, uri, method='GET', params=None, json=None): |
|
138 |
url = urljoin(self.base_url, uri) |
|
139 |
headers = {'Accept': 'application/json; charset=utf-8'} |
|
140 | ||
141 |
if self.secret_key: |
|
142 |
des_key = pad(force_bytes(self.secret_key), 8)[:8] |
|
143 |
cipher = DES.new(des_key, DES.MODE_ECB) |
|
144 |
epoch = int(time() * 1000) |
|
145 |
plaintext = '{"caller":"%s","createInfo":%i}' % (self.secret_id, epoch) |
|
146 |
msg = cipher.encrypt(pad(force_bytes(plaintext), 8)) |
|
147 |
headers['token_info_caller'] = base64.b64encode(msg) |
|
148 | ||
149 |
response = self.requests.request(method=method, url=url, headers=headers, params=params, json=json) |
|
150 | ||
151 |
# handle strange 304 delete response |
|
152 |
if method == 'DELETE' and response.status_code == 304: |
|
153 |
raise APIError('Appointment not found') |
|
154 | ||
155 |
# handle 500 generated by wrong token as a succesfull ping response |
|
156 |
if uri == 'sites/' and response.status_code == 500: |
|
157 |
response.status_code = 200 |
|
158 | ||
159 |
if response.status_code != 200: |
|
160 |
try: |
|
161 |
json_content = response.json() |
|
162 |
except ValueError: |
|
163 |
json_content = None |
|
164 |
raise APIError( |
|
165 |
'error status:%s %r, content:%r' |
|
166 |
% (response.status_code, response.reason, response.text[:1024]), |
|
167 |
data={'status_code': response.status_code, 'json_content': json_content}, |
|
168 |
) |
|
169 |
return response |
|
170 | ||
171 |
def check_status(self): |
|
172 |
""" |
|
173 |
Raise an exception if something goes wrong. |
|
174 |
""" |
|
175 |
self.request('sites/', method='GET') |
|
176 | ||
177 |
@endpoint( |
|
178 |
display_category=_('Appointment'), |
|
179 |
description=_('Create appointment'), |
|
180 |
name='create-appointment', |
|
181 |
perm='can_access', |
|
182 |
methods=['post'], |
|
183 |
post={'request_body': {'schema': {'application/json': CREATE_APPOINTMENT_SCHEMA}}}, |
|
184 |
) |
|
185 |
def create_appointment(self, request, post_data): |
|
186 |
# address dict is required |
|
187 |
if not post_data.get('user'): |
|
188 |
post_data['user'] = {} |
|
189 |
if not post_data['user'].get('address'): |
|
190 |
post_data['user']['address'] = {} |
|
191 | ||
192 |
response = self.request('appointments/', method='POST', json=post_data) |
|
193 |
return {'data': {'id': response.text}} |
|
194 | ||
195 |
@endpoint( |
|
196 |
display_category=_('Appointment'), |
|
197 |
description=_('Get appointment'), |
|
198 |
name='get-appointment', |
|
199 |
perm='can_access', |
|
200 |
methods=['get'], |
|
201 |
pattern='^(?P<id>\w+)/$', |
|
202 |
example_pattern='{id}/', |
|
203 |
parameters={ |
|
204 |
'id': { |
|
205 |
'description': _('Appointment id returned by create-appointment endpoint'), |
|
206 |
'example_value': '94PEP4', |
|
207 |
}, |
|
208 |
}, |
|
209 |
) |
|
210 |
def get_appointment(self, request, id): |
|
211 |
response = self.request('appointments/%s/' % id, method='GET') |
|
212 |
return {'data': response.json()} |
|
213 | ||
214 |
@endpoint( |
|
215 |
display_category=_('Appointment'), |
|
216 |
description=_('Delete appointment'), |
|
217 |
name='delete-appointment', |
|
218 |
perm='can_access', |
|
219 |
methods=['delete'], |
|
220 |
pattern='^(?P<id>\w+)/$', |
|
221 |
example_pattern='{id}/', |
|
222 |
parameters={ |
|
223 |
'id': { |
|
224 |
'description': _('Appointment id returned by create-appointment endpoint'), |
|
225 |
'example_value': '94PEP4', |
|
226 |
}, |
|
227 |
}, |
|
228 |
) |
|
229 |
def delete_appointment(self, request, id): |
|
230 |
response = self.request('appointments/%s/' % id, method='DELETE') |
|
231 |
return {'data': None} |
passerelle/settings.py | ||
---|---|---|
131 | 131 |
'passerelle.apps.bdp', |
132 | 132 |
'passerelle.apps.cartads_cs', |
133 | 133 |
'passerelle.apps.choosit', |
134 | 134 |
'passerelle.apps.cityweb', |
135 | 135 |
'passerelle.apps.clicrdv', |
136 | 136 |
'passerelle.apps.cmis', |
137 | 137 |
'passerelle.apps.cryptor', |
138 | 138 |
'passerelle.apps.csvdatasource', |
139 |
'passerelle.apps.esirius', |
|
139 | 140 |
'passerelle.apps.family', |
140 | 141 |
'passerelle.apps.feeds', |
141 | 142 |
'passerelle.apps.gdc', |
142 | 143 |
'passerelle.apps.gesbac', |
143 | 144 |
'passerelle.apps.jsondatastore', |
144 | 145 |
'passerelle.apps.sp_fr', |
145 | 146 |
'passerelle.apps.maelis', |
146 | 147 |
'passerelle.apps.mdel', |
tests/test_esirius.py | ||
---|---|---|
1 |
# passerelle - uniform access to multiple data sources and services |
|
2 |
# Copyright (C) 2021 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 |
import httmock |
|
19 |
import pytest |
|
20 | ||
21 |
from passerelle.apps.esirius.models import ESirius |
|
22 |
from passerelle.utils.jsonresponse import APIError |
|
23 | ||
24 |
from test_manager import login |
|
25 |
import utils |
|
26 | ||
27 | ||
28 |
CREATE_APPOINTMENT_PAYLOAD = { |
|
29 |
'beginDate': '2021-02-24', |
|
30 |
'beginTime': '16:40', |
|
31 |
'endDate': '2021-02-24', |
|
32 |
'endTime': '17:00', |
|
33 |
'comment': 'commentaire', |
|
34 |
'isoLanguage': 'fr', |
|
35 |
'needsConfirmation': 'False', |
|
36 |
'rdvChannel': 'WEBSERVICES', |
|
37 |
'receptionChannel': 'WS', |
|
38 |
'serviceId': '9', |
|
39 |
'siteCode': 'site1', |
|
40 |
'resources': { |
|
41 |
'id': '1', |
|
42 |
'key': '17', |
|
43 |
'type': 'STATION', |
|
44 |
}, |
|
45 |
} |
|
46 | ||
47 |
GET_APPOINTMENT_RESPONSE = ''' |
|
48 |
{ |
|
49 |
"beginDate" : "2021-02-26", |
|
50 |
"beginTime" : "16:40", |
|
51 |
"codeRDV" : "94PEP4", |
|
52 |
"comment" : "coucou", |
|
53 |
"endDate" : "2021-02-26", |
|
54 |
"endTime" : "17:00", |
|
55 |
"idSys" : 108840, |
|
56 |
"isoLanguage" : "fr", |
|
57 |
"motives" : [], |
|
58 |
"needsConfirmation" : false, |
|
59 |
"rdvChannel" : "EAPP0", |
|
60 |
"receptionChannel" : "WS", |
|
61 |
"resources" : { |
|
62 |
"id" : 29, |
|
63 |
"key" : "46", |
|
64 |
"name" : "C1", |
|
65 |
"type" : "STATION" |
|
66 |
}, |
|
67 |
"serviceId" : "39", |
|
68 |
"siteCode" : "site1", |
|
69 |
"siteIdSys" : 5, |
|
70 |
"user" : { |
|
71 |
"additionalPersonalIdentity" : [], |
|
72 |
"address" : {}, |
|
73 |
"civility" : "", |
|
74 |
"idSys" : "95897" |
|
75 |
} |
|
76 |
} |
|
77 |
''' |
|
78 | ||
79 | ||
80 |
@pytest.fixture |
|
81 |
def connector(db): |
|
82 |
return utils.setup_access_rights( |
|
83 |
ESirius.objects.create( |
|
84 |
slug='test', secret_id='xxx', secret_key='yyy', base_url='https://dummy-server.org' |
|
85 |
) |
|
86 |
) |
|
87 | ||
88 | ||
89 |
def get_endpoint(name): |
|
90 |
return utils.generic_endpoint_url('esirius', name) |
|
91 | ||
92 | ||
93 |
@pytest.mark.freeze_time('2021-01-26 15:13:6.880') # epoch + 1611673986.88 s |
|
94 |
def test_token(connector): |
|
95 |
connector.secret_id = 'eAppointment' |
|
96 |
connector.secret_key = 'ES2I Info Caller Http Encryption Key' |
|
97 |
connector.save() |
|
98 | ||
99 |
@httmock.all_requests |
|
100 |
def esirius_mock(url, request): |
|
101 |
assert ( |
|
102 |
request.headers['token_info_caller'] |
|
103 |
== b'yM4zYAxT67Qvjd20riG3j0eu0t0Ku+HLlttj17Gul7zkruFaXX1J8BJ6sV2Ldgw40axfWh+ESAY=' |
|
104 |
) |
|
105 |
return httmock.response(200) |
|
106 | ||
107 |
with httmock.HTTMock(esirius_mock): |
|
108 |
connector.request('an/uri/', method='get', params="somes") |
|
109 | ||
110 | ||
111 |
@pytest.mark.parametrize('secret_key', ['xxx', '']) |
|
112 |
def test_pre_request(connector, secret_key): |
|
113 |
@httmock.urlmatch(netloc='dummy-server.org', path='/an/uri/', method='GET') |
|
114 |
def esirius_mock(url, request): |
|
115 |
assert request.headers['Accept'] == 'application/json; charset=utf-8' |
|
116 |
assert bool(request.headers.get('token_info_caller')) == bool(secret_key) |
|
117 |
if secret_key: |
|
118 |
assert request.headers['token_info_caller'][:42] == b'f3G6sjRZETBam6vcdrAxmvJQTX5hh6OjZ8XlUO6SMo' |
|
119 |
return httmock.response(200) |
|
120 | ||
121 |
connector.secret_key = secret_key |
|
122 |
connector.save() |
|
123 |
with httmock.HTTMock(esirius_mock): |
|
124 |
connector.request('an/uri/', method='get', params="somes") |
|
125 | ||
126 | ||
127 |
@pytest.mark.parametrize( |
|
128 |
'status_code, content, a_dict', |
|
129 |
[ |
|
130 |
(400, '{"message": "help"}', {'message': 'help'}), |
|
131 |
(500, 'not json', None), |
|
132 |
], |
|
133 |
) |
|
134 |
def test_post_request(connector, status_code, content, a_dict): |
|
135 |
@httmock.urlmatch(netloc='dummy-server.org', path='/an/uri/', method='GET') |
|
136 |
def esirius_mock(url, request): |
|
137 |
return httmock.response(status_code, content) |
|
138 | ||
139 |
with pytest.raises(APIError) as exc: |
|
140 |
with httmock.HTTMock(esirius_mock): |
|
141 |
connector.request('an/uri/', params="somes") |
|
142 | ||
143 |
assert exc.value.err |
|
144 |
assert exc.value.data['status_code'] == status_code |
|
145 |
assert exc.value.data['json_content'] == a_dict |
|
146 | ||
147 | ||
148 |
@pytest.mark.parametrize('status_code, is_up', [(200, True), (500, True), (503, False)]) |
|
149 |
@pytest.mark.parametrize('content', ['wathever', '{"message": "help"}']) |
|
150 |
def test_check_status(app, connector, status_code, is_up, content): |
|
151 |
@httmock.all_requests |
|
152 |
def esirius_mock(url, request): |
|
153 |
return httmock.response(status_code, content) |
|
154 | ||
155 |
if is_up: |
|
156 |
with httmock.HTTMock(esirius_mock): |
|
157 |
connector.check_status() |
|
158 |
else: |
|
159 |
with pytest.raises(APIError): |
|
160 |
with httmock.HTTMock(esirius_mock): |
|
161 |
connector.check_status() |
|
162 | ||
163 | ||
164 |
def test_create_appointment(app, connector): |
|
165 |
endpoint = get_endpoint('create-appointment') |
|
166 | ||
167 |
@httmock.urlmatch(netloc='dummy-server.org', path='/appointments/', method='POST') |
|
168 |
def esirius_mock(url, request): |
|
169 |
return httmock.response(200, b'94PEP4') |
|
170 | ||
171 |
with httmock.HTTMock(esirius_mock): |
|
172 |
resp = app.post_json(endpoint, params=CREATE_APPOINTMENT_PAYLOAD) |
|
173 | ||
174 |
assert not resp.json['err'] |
|
175 |
assert resp.json['data'] == {'id': '94PEP4'} |
|
176 | ||
177 | ||
178 |
def test_create_appointment_error_404(app, connector): |
|
179 |
endpoint = get_endpoint('create-appointment') |
|
180 | ||
181 |
# payload not providing or providing an unconfigured serviceId |
|
182 |
payload = CREATE_APPOINTMENT_PAYLOAD |
|
183 |
del payload['serviceId'] |
|
184 | ||
185 |
@httmock.urlmatch(netloc='dummy-server.org', path='/appointments/', method='POST') |
|
186 |
def esirius_mock(url, request): |
|
187 |
return httmock.response( |
|
188 |
404, |
|
189 |
{ |
|
190 |
'code': 'Not Found', |
|
191 |
'type': 'com.es2i.planning.api.exception.NoService4RDVException', |
|
192 |
'message': "Le rendez-vous {0} n'a pas créé", |
|
193 |
}, |
|
194 |
) |
|
195 | ||
196 |
with httmock.HTTMock(esirius_mock): |
|
197 |
resp = app.post_json(endpoint, params=payload) |
|
198 | ||
199 |
assert resp.json['err'] |
|
200 |
assert resp.json['data']['status_code'] == 404 |
|
201 |
assert resp.json['data']['json_content'] == { |
|
202 |
'code': 'Not Found', |
|
203 |
'type': 'com.es2i.planning.api.exception.NoService4RDVException', |
|
204 |
'message': "Le rendez-vous {0} n'a pas créé", |
|
205 |
} |
|
206 | ||
207 | ||
208 |
def test_create_appointment_error_500(app, connector): |
|
209 |
endpoint = get_endpoint('create-appointment') |
|
210 | ||
211 |
# payload not providing beginTime |
|
212 |
payload = {'beginDate': '2021-02-23'} |
|
213 | ||
214 |
@httmock.urlmatch(netloc='dummy-server.org', path='/appointments/', method='POST') |
|
215 |
def esirius_mock(url, request): |
|
216 |
return httmock.response(500, 'java stack') |
|
217 | ||
218 |
with httmock.HTTMock(esirius_mock): |
|
219 |
resp = app.post_json(endpoint, params=payload) |
|
220 | ||
221 |
assert resp.json['err'] |
|
222 |
assert resp.json['err_class'] == 'passerelle.utils.jsonresponse.APIError' |
|
223 |
assert resp.json['err_desc'] == "error status:500 None, content:'java stack'" |
|
224 |
assert resp.json['data']['status_code'] == 500 |
|
225 |
assert resp.json['data']['json_content'] is None |
|
226 | ||
227 | ||
228 |
def test_get_appointment(app, connector): |
|
229 |
endpoint = get_endpoint('get-appointment') |
|
230 | ||
231 |
@httmock.urlmatch(netloc='dummy-server.org', path='/appointments/94PEP4/', method='GET') |
|
232 |
def esirius_mock(url, request): |
|
233 |
return httmock.response(200, GET_APPOINTMENT_RESPONSE) |
|
234 | ||
235 |
with httmock.HTTMock(esirius_mock): |
|
236 |
resp = app.get(endpoint + '/94PEP4/') |
|
237 | ||
238 |
assert not resp.json['err'] |
|
239 |
assert resp.json['data']['codeRDV'] == '94PEP4' |
|
240 |
assert resp.json['data'] == json.loads(GET_APPOINTMENT_RESPONSE) |
|
241 | ||
242 | ||
243 |
def test_get_appointment_error(app, connector): |
|
244 |
endpoint = get_endpoint('get-appointment') |
|
245 | ||
246 |
@httmock.urlmatch(netloc='dummy-server.org', path='/appointments/94PEP4/', method='GET') |
|
247 |
def esirius_mock(url, request): |
|
248 |
return httmock.response(404, '{"code":"Not Found","message":"Le rendez-vous {0} n\'existe pas"}') |
|
249 | ||
250 |
with httmock.HTTMock(esirius_mock): |
|
251 |
resp = app.get(endpoint + '/94PEP4/') |
|
252 | ||
253 |
assert resp.json['err'] |
|
254 |
assert resp.json['err_class'] == 'passerelle.utils.jsonresponse.APIError' |
|
255 |
assert resp.json['data']['status_code'] == 404 |
|
256 |
assert resp.json['data']['json_content'] == { |
|
257 |
"code": "Not Found", |
|
258 |
"message": "Le rendez-vous {0} n'existe pas", |
|
259 |
} |
|
260 | ||
261 | ||
262 |
def test_delete_appointment(app, connector): |
|
263 |
endpoint = get_endpoint('delete-appointment') |
|
264 | ||
265 |
@httmock.urlmatch(netloc='dummy-server.org', path='/appointments/94PEP4/', method='DELETE') |
|
266 |
def esirius_mock(url, request): |
|
267 |
return httmock.response(200, b'') |
|
268 | ||
269 |
with httmock.HTTMock(esirius_mock): |
|
270 |
resp = app.delete(endpoint + '/94PEP4/') |
|
271 | ||
272 |
assert not resp.json['err'] |
|
273 |
assert resp.json['data'] == None |
|
274 | ||
275 | ||
276 |
def test_delete_appointment_error(app, connector): |
|
277 |
endpoint = get_endpoint('delete-appointment') |
|
278 | ||
279 |
@httmock.urlmatch(netloc='dummy-server.org', path='/appointments/94PEP4/', method='DELETE') |
|
280 |
def esirius_mock(url, request): |
|
281 |
return httmock.response(304, b'') |
|
282 | ||
283 |
with httmock.HTTMock(esirius_mock): |
|
284 |
resp = app.delete(endpoint + '/94PEP4/') |
|
285 | ||
286 |
assert resp.json['err'] |
|
287 |
assert resp.json['err_desc'] == 'Appointment not found' |
|
288 | ||
289 | ||
290 |
def test_manager(db, app, admin_user, connector): |
|
291 |
url = '/%s/%s/' % (connector.get_connector_slug(), connector.slug) |
|
292 |
connector.title = 'Test' |
|
293 |
connector.description = 'Test eSirius' |
|
294 |
connector.save() |
|
295 |
login(app) |
|
296 |
resp = app.get(url) |
|
297 |
resp = resp.click('edit') |
|
298 |
assert resp.html.find('div', {'id': 'id_secret_key_p'}).input['value'] == 'yyy' |
|
299 |
resp = resp.form.submit() |
|
300 |
assert ( |
|
301 |
'DES key must be 8 bytes long' |
|
302 |
in resp.html.find('div', {'id': 'id_secret_key_p'}).find('div', {'class': 'error'}).text |
|
303 |
) |
|
304 |
resp.form['secret_key'] = '8 bytes!' |
|
305 |
resp = resp.form.submit() |
|
306 |
assert ESirius.objects.get().secret_key == '8 bytes!' |
|
307 | ||
308 |
# accept an empty key |
|
309 |
resp = app.get(url) |
|
310 |
resp = resp.click('edit') |
|
311 |
assert resp.html.find('div', {'id': 'id_secret_key_p'}).input['value'] == '8 bytes!' |
|
312 |
resp.form['secret_key'] = '' |
|
313 |
resp = resp.form.submit() |
|
314 |
assert ESirius.objects.get().secret_key == '' |
|
0 |
- |