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 datetime import datetime |
|
20 |
from time import time |
|
21 |
from urllib.parse import urljoin |
|
22 | ||
23 |
from Cryptodome.Cipher import DES |
|
24 |
from Cryptodome.Util.Padding import pad |
|
25 | ||
26 |
from django.db import models |
|
27 |
from django.core.exceptions import ValidationError |
|
28 |
from django.utils.encoding import force_bytes |
|
29 |
from django.utils.translation import ugettext_lazy as _ |
|
30 | ||
31 |
from passerelle.base.models import BaseResource, HTTPResource |
|
32 |
from passerelle.utils.api import endpoint |
|
33 |
from passerelle.utils.jsonresponse import APIError |
|
34 | ||
35 | ||
36 |
CREATE_APPOINTMENT_SCHEMA = { |
|
37 |
'$schema': 'http://json-schema.org/draft-04/schema#', |
|
38 |
"type": "object", |
|
39 |
'properties': { |
|
40 |
'idSys': {'type': 'string', 'pattern': '^[0-9]*$'}, |
|
41 |
'CodeRDV': {'type': 'string'}, |
|
42 |
'beginDate': {'type': 'string', 'pattern': '^[0-9]{4}-[0-9]{2}-[0-9]{2}$'}, |
|
43 |
'beginTime': {'type': 'string', 'pattern': '^[0-9]{2}:[0-9]{2}$'}, |
|
44 |
'endDate': {'type': 'string', 'pattern': '^[0-9]{4}-[0-9]{2}-[0-9]{2}$'}, |
|
45 |
'endTime': {'type': 'string', 'pattern': '^[0-9]{2}:[0-9]{2}$'}, |
|
46 |
'comment': {'type': 'string'}, |
|
47 |
'isoLanguage': {'description': 'ex: fr', 'type': 'string'}, |
|
48 |
'needsConfirmation': {'description': 'boolean expected', 'type': 'string'}, |
|
49 |
'rdvChannel': {'description': 'ex: EAPP0', 'type': 'string'}, |
|
50 |
'receptionChannel': {'type': 'string'}, |
|
51 |
'owner': {'type': 'object', 'properties': {'key': {'type': 'string'}, 'value': {'type': 'string'}}}, |
|
52 |
'user': { |
|
53 |
'type': 'object', |
|
54 |
'properties': { |
|
55 |
'idSys': {'type': 'string', 'pattern': '^[0-9]*$'}, |
|
56 |
'personalIdentity': {'type': 'string'}, |
|
57 |
'additionalPersonalIdentity': {"type": "array", "items": {'type': 'string'}}, |
|
58 |
'lastName': {'type': 'string'}, |
|
59 |
'civility': {'type': 'string'}, |
|
60 |
'firstName': {'type': 'string'}, |
|
61 |
'birthday': {'type': 'string'}, |
|
62 |
'email': {'type': 'string'}, |
|
63 |
'fixPhone': {'type': 'string'}, |
|
64 |
'phone': {'type': 'string'}, |
|
65 |
'address': { |
|
66 |
'type': 'object', |
|
67 |
'properties': { |
|
68 |
'line1': {'type': 'string'}, |
|
69 |
'line2': {'type': 'string'}, |
|
70 |
'zipCode': {'type': 'string'}, |
|
71 |
'city': {'type': 'string'}, |
|
72 |
'country': {'type': 'string'}, |
|
73 |
}, |
|
74 |
}, |
|
75 |
}, |
|
76 |
}, |
|
77 |
'serviceId': {'type': 'string'}, |
|
78 |
'siteCode': {'type': 'string'}, |
|
79 |
"resources": { |
|
80 |
'type': 'object', |
|
81 |
'properties': { |
|
82 |
'id': {'type': 'string', 'pattern': '^[0-9]*$'}, |
|
83 |
'key': {'type': 'string'}, |
|
84 |
'type': {'type': 'string'}, |
|
85 |
'name': {'type': 'string'}, |
|
86 |
'station': { |
|
87 |
'type': 'object', |
|
88 |
'properties': { |
|
89 |
'id': {'type': 'string', 'pattern': '^[0-9]*$'}, |
|
90 |
'key': {'type': 'string'}, |
|
91 |
'name': {'type': 'string'}, |
|
92 |
}, |
|
93 |
}, |
|
94 |
}, |
|
95 |
}, |
|
96 |
'motives': { |
|
97 |
"type": "array", |
|
98 |
"items": { |
|
99 |
'type': 'object', |
|
100 |
'properties': { |
|
101 |
'id': {'type': 'string', 'pattern': '^[0-9]*$'}, |
|
102 |
'name': {'type': 'string'}, |
|
103 |
'shortName': {'type': 'string'}, |
|
104 |
'processingTime': {'type': 'string', 'pattern': '^[0-9]*$'}, |
|
105 |
'externalModuleAccess': {'type': 'string', 'pattern': '^[0-9]*$'}, |
|
106 |
'quantity': {'type': 'string', 'pattern': '^[0-9]*$'}, |
|
107 |
'usePremotiveQuantity': {'description': 'boolean expected', 'type': 'string'}, |
|
108 |
}, |
|
109 |
}, |
|
110 |
}, |
|
111 |
}, |
|
112 |
'unflatten': True, |
|
113 |
} |
|
114 | ||
115 | ||
116 |
class DESKeyModel(models.CharField): |
|
117 |
def clean(self, value, model_instance): |
|
118 |
if len(value) < 8: |
|
119 |
raise ValidationError(_('DES key must be 8 bytes long (longer keys are truncated)')) |
|
120 |
return super().clean(value, model_instance) |
|
121 | ||
122 | ||
123 |
class ESirius(BaseResource, HTTPResource): |
|
124 |
secret_id = models.CharField(max_length=128, verbose_name=_('Application identifier'), blank=True) |
|
125 |
secret_key = DESKeyModel(max_length=128, verbose_name=_('Secret Key'), blank=True) |
|
126 |
base_url = models.CharField( |
|
127 |
max_length=256, |
|
128 |
blank=False, |
|
129 |
verbose_name=_('ePlanning webservices URL'), |
|
130 |
help_text=_('example: https://HOST/ePlanning/webservices/api/'), |
|
131 |
) |
|
132 | ||
133 |
category = _('Business Process Connectors') |
|
134 | ||
135 |
class Meta: |
|
136 |
verbose_name = _('eSirius') |
|
137 | ||
138 |
def request(self, uri, method='get', params=None, json=None): |
|
139 |
url = urljoin(self.base_url, uri) |
|
140 | ||
141 |
des_key = pad(force_bytes(self.secret_key), 8)[:8] |
|
142 |
cipher = DES.new(des_key, DES.MODE_ECB) |
|
143 |
epoch = int(time() * 1000) |
|
144 |
plaintext = '{"caller":"%s","createInfo":%i}' % (self.secret_id, epoch) |
|
145 |
msg = cipher.encrypt(pad(force_bytes(plaintext), 8)) |
|
146 |
headers = { |
|
147 |
'Accept': 'application/json; charset=utf-8', |
|
148 |
'token_info_caller': base64.b64encode(msg), |
|
149 |
} |
|
150 | ||
151 |
if method == 'get': |
|
152 |
response = self.requests.get(url, headers=headers, params=params) |
|
153 |
elif method == 'post': |
|
154 |
response = self.requests.post(url, headers=headers, json=json) |
|
155 |
elif method == 'delete': |
|
156 |
response = self.requests.delete(url, headers=headers) |
|
157 |
if response.status_code == 304: # handle strange 304 delete response |
|
158 |
raise APIError('Appointment not found') |
|
159 |
else: |
|
160 |
raise APIError('Method Not Allowed', http_status=405) |
|
161 | ||
162 |
if response.status_code != 200: |
|
163 |
try: |
|
164 |
json_content = response.json() |
|
165 |
except ValueError: |
|
166 |
json_content = None |
|
167 |
raise APIError( |
|
168 |
'error status:%s %r, content:%r' |
|
169 |
% (response.status_code, response.reason, response.text[:1024]), |
|
170 |
data={'status_code': response.status_code, 'json_content': json_content}, |
|
171 |
) |
|
172 |
return response |
|
173 | ||
174 |
def check_status(self): |
|
175 |
""" |
|
176 |
Raise an exception if something goes wrong. |
|
177 |
""" |
|
178 |
self.request('sites/', method='get') |
|
179 | ||
180 |
@endpoint( |
|
181 |
display_category=_('Appointment'), |
|
182 |
name='create-appointment', |
|
183 |
perm='can_access', |
|
184 |
methods=['post'], |
|
185 |
description=_('Create appointment'), |
|
186 |
post={'request_body': {'schema': {'application/json': CREATE_APPOINTMENT_SCHEMA}}}, |
|
187 |
) |
|
188 |
def create_appointment(self, request, post_data): |
|
189 |
# address dict is required |
|
190 |
if not post_data.get('user'): |
|
191 |
post_data['user'] = {} |
|
192 |
if not post_data['user'].get('address'): |
|
193 |
post_data['user']['address'] = {} |
|
194 | ||
195 |
response = self.request('appointments/', method='post', json=post_data) |
|
196 |
return {'data': {'id': response.text}} |
|
197 | ||
198 |
@endpoint( |
|
199 |
display_category=_('Appointment'), |
|
200 |
name='get-appointment', |
|
201 |
perm='can_access', |
|
202 |
methods=['get'], |
|
203 |
description=_('Get appointment'), |
|
204 |
parameters={ |
|
205 |
'id': {'description': _('Appointment id returned by create-appointment endpoint')}, |
|
206 |
}, |
|
207 |
) |
|
208 |
def get_appointment(self, request, id): |
|
209 |
response = self.request('appointments/%s/' % id, method='get') |
|
210 |
return {'data': response.json()} |
|
211 | ||
212 |
@endpoint( |
|
213 |
display_category=_('Appointment'), |
|
214 |
name='delete-appointment', |
|
215 |
perm='can_access', |
|
216 |
methods=['post'], |
|
217 |
description=_('Delete appointment'), |
|
218 |
parameters={ |
|
219 |
'id': {'description': _('Appointment id returned by create-appointment endpoint')}, |
|
220 |
}, |
|
221 |
) |
|
222 |
def delete_appointment(self, request, id): |
|
223 |
response = self.request('appointments/%s/' % id, method='delete') |
|
224 |
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" : "943A98", |
|
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 |
def test_pre_request(connector): |
|
112 |
@httmock.urlmatch(netloc='dummy-server.org', path='/an/uri/', method='GET') |
|
113 |
def esirius_mock(url, request): |
|
114 |
assert request.headers['Accept'] == 'application/json; charset=utf-8' |
|
115 |
assert request.headers['token_info_caller'][:42] == b'f3G6sjRZETBam6vcdrAxmvJQTX5hh6OjZ8XlUO6SMo' |
|
116 |
return httmock.response(200) |
|
117 | ||
118 |
with httmock.HTTMock(esirius_mock): |
|
119 |
connector.request('an/uri/', method='get', params="somes") |
|
120 | ||
121 | ||
122 |
@pytest.mark.parametrize( |
|
123 |
'method, status_code', |
|
124 |
[ |
|
125 |
('get', 200), |
|
126 |
('post', 200), |
|
127 |
('delete', 200), |
|
128 |
('put', 405), |
|
129 |
], |
|
130 |
) |
|
131 |
def test_request_method(connector, method, status_code): |
|
132 |
@httmock.urlmatch(netloc='dummy-server.org', path='/an/uri/', method=method.upper()) |
|
133 |
def esirius_mock(url, request): |
|
134 |
return httmock.response(status_code, '{}') |
|
135 | ||
136 |
if status_code == 200: |
|
137 |
with httmock.HTTMock(esirius_mock): |
|
138 |
resp = connector.request('an/uri/', method=method) |
|
139 |
assert resp.status_code == 200 |
|
140 |
else: |
|
141 |
with pytest.raises(APIError, match='Method Not Allowed') as exc: |
|
142 |
with httmock.HTTMock(esirius_mock): |
|
143 |
connector.request('an/uri/', method=method) |
|
144 |
assert exc.value.err |
|
145 |
assert exc.value.http_status == status_code |
|
146 | ||
147 | ||
148 |
@pytest.mark.parametrize( |
|
149 |
'status_code, content, a_dict', |
|
150 |
[ |
|
151 |
(400, '{"message": "help"}', {'message': 'help'}), |
|
152 |
(500, 'not json', None), |
|
153 |
], |
|
154 |
) |
|
155 |
def test_post_request(connector, status_code, content, a_dict): |
|
156 |
@httmock.urlmatch(netloc='dummy-server.org', path='/an/uri/', method='GET') |
|
157 |
def esirius_mock(url, request): |
|
158 |
return httmock.response(status_code, content) |
|
159 | ||
160 |
with pytest.raises(APIError) as exc: |
|
161 |
with httmock.HTTMock(esirius_mock): |
|
162 |
connector.request('an/uri/', params="somes") |
|
163 | ||
164 |
assert exc.value.err |
|
165 |
assert exc.value.data['status_code'] == status_code |
|
166 |
assert exc.value.data['json_content'] == a_dict |
|
167 | ||
168 | ||
169 |
@pytest.mark.parametrize( |
|
170 |
'status_code, content, is_up', |
|
171 |
[ |
|
172 |
(200, 'wathever', True), |
|
173 |
(500, '{"message": "help"}', False), |
|
174 |
], |
|
175 |
) |
|
176 |
def test_check_status(app, connector, status_code, content, is_up): |
|
177 |
@httmock.all_requests |
|
178 |
def esirius_mock(url, request): |
|
179 |
return httmock.response(status_code, content) |
|
180 | ||
181 |
if is_up: |
|
182 |
with httmock.HTTMock(esirius_mock): |
|
183 |
connector.check_status() |
|
184 |
else: |
|
185 |
with pytest.raises(APIError): |
|
186 |
with httmock.HTTMock(esirius_mock): |
|
187 |
connector.check_status() |
|
188 | ||
189 | ||
190 |
def test_create_appointment(app, connector): |
|
191 |
endpoint = get_endpoint('create-appointment') |
|
192 | ||
193 |
@httmock.urlmatch(netloc='dummy-server.org', path='/appointments/', method='POST') |
|
194 |
def esirius_mock(url, request): |
|
195 |
return httmock.response(200, b'94PEP4') |
|
196 | ||
197 |
with httmock.HTTMock(esirius_mock): |
|
198 |
resp = app.post_json(endpoint, params=CREATE_APPOINTMENT_PAYLOAD) |
|
199 | ||
200 |
assert not resp.json['err'] |
|
201 |
assert resp.json['data'] == {'id': '94PEP4'} |
|
202 | ||
203 | ||
204 |
def test_create_appointment_error_404(app, connector): |
|
205 |
endpoint = get_endpoint('create-appointment') |
|
206 | ||
207 |
# payload not providing or providing an unconfigured serviceId |
|
208 |
payload = CREATE_APPOINTMENT_PAYLOAD |
|
209 |
del payload['serviceId'] |
|
210 | ||
211 |
@httmock.urlmatch(netloc='dummy-server.org', path='/appointments/', method='POST') |
|
212 |
def esirius_mock(url, request): |
|
213 |
return httmock.response( |
|
214 |
404, |
|
215 |
{ |
|
216 |
'code': 'Not Found', |
|
217 |
'type': 'com.es2i.planning.api.exception.NoService4RDVException', |
|
218 |
'message': "Le rendez-vous {0} n'a pas créé", |
|
219 |
}, |
|
220 |
) |
|
221 | ||
222 |
with httmock.HTTMock(esirius_mock): |
|
223 |
resp = app.post_json(endpoint, params=payload) |
|
224 | ||
225 |
assert resp.json['err'] |
|
226 |
assert resp.json['data']['status_code'] == 404 |
|
227 |
assert resp.json['data']['json_content'] == { |
|
228 |
'code': 'Not Found', |
|
229 |
'type': 'com.es2i.planning.api.exception.NoService4RDVException', |
|
230 |
'message': "Le rendez-vous {0} n'a pas créé", |
|
231 |
} |
|
232 | ||
233 | ||
234 |
def test_create_appointment_error_500(app, connector): |
|
235 |
endpoint = get_endpoint('create-appointment') |
|
236 | ||
237 |
# payload not providing beginTime |
|
238 |
payload = {'beginDate': '2021-02-23'} |
|
239 | ||
240 |
@httmock.urlmatch(netloc='dummy-server.org', path='/appointments/', method='POST') |
|
241 |
def esirius_mock(url, request): |
|
242 |
return httmock.response(500, 'java stack') |
|
243 | ||
244 |
with httmock.HTTMock(esirius_mock): |
|
245 |
resp = app.post_json(endpoint, params=payload) |
|
246 | ||
247 |
assert resp.json['err'] |
|
248 |
assert resp.json['err_class'] == 'passerelle.utils.jsonresponse.APIError' |
|
249 |
assert resp.json['err_desc'] == "error status:500 None, content:'java stack'" |
|
250 |
assert resp.json['data']['status_code'] == 500 |
|
251 |
assert resp.json['data']['json_content'] is None |
|
252 | ||
253 | ||
254 |
def test_get_appointment(app, connector): |
|
255 |
endpoint = get_endpoint('get-appointment') |
|
256 | ||
257 |
@httmock.urlmatch(netloc='dummy-server.org', path='/appointments/', method='GET') |
|
258 |
def esirius_mock(url, request): |
|
259 |
return httmock.response(200, GET_APPOINTMENT_RESPONSE) |
|
260 | ||
261 |
with httmock.HTTMock(esirius_mock): |
|
262 |
resp = app.get(endpoint + '?id=94PEP4') |
|
263 | ||
264 |
assert not resp.json['err'] |
|
265 |
assert resp.json['data']['codeRDV'] |
|
266 |
assert resp.json['data'] == json.loads(GET_APPOINTMENT_RESPONSE) |
|
267 | ||
268 | ||
269 |
def test_get_appointment_error(app, connector): |
|
270 |
endpoint = get_endpoint('get-appointment') |
|
271 | ||
272 |
@httmock.urlmatch(netloc='dummy-server.org', path='/appointments/', method='GET') |
|
273 |
def esirius_mock(url, request): |
|
274 |
return httmock.response(404, '{"code":"Not Found","message":"Le rendez-vous {0} n\'existe pas"}') |
|
275 | ||
276 |
with httmock.HTTMock(esirius_mock): |
|
277 |
resp = app.get(endpoint + '?id=94PEP4') |
|
278 | ||
279 |
assert resp.json['err'] |
|
280 |
assert resp.json['err_class'] == 'passerelle.utils.jsonresponse.APIError' |
|
281 |
assert resp.json['data']['status_code'] == 404 |
|
282 |
assert resp.json['data']['json_content'] == { |
|
283 |
"code": "Not Found", |
|
284 |
"message": "Le rendez-vous {0} n'existe pas", |
|
285 |
} |
|
286 | ||
287 | ||
288 |
def test_delete_appointment(app, connector): |
|
289 |
endpoint = get_endpoint('delete-appointment') |
|
290 | ||
291 |
@httmock.urlmatch(netloc='dummy-server.org', path='/appointments/', method='DELETE') |
|
292 |
def esirius_mock(url, request): |
|
293 |
return httmock.response(200, b'') |
|
294 | ||
295 |
with httmock.HTTMock(esirius_mock): |
|
296 |
resp = app.post_json(endpoint + '?id=94PEP4') |
|
297 | ||
298 |
assert not resp.json['err'] |
|
299 |
assert resp.json['data'] == None |
|
300 | ||
301 | ||
302 |
def test_delete_appointment_error(app, connector): |
|
303 |
endpoint = get_endpoint('delete-appointment') |
|
304 | ||
305 |
@httmock.urlmatch(netloc='dummy-server.org', path='/appointments/', method='DELETE') |
|
306 |
def esirius_mock(url, request): |
|
307 |
return httmock.response(304, b'') |
|
308 | ||
309 |
with httmock.HTTMock(esirius_mock): |
|
310 |
resp = app.post_json(endpoint + '?id=94PEP4') |
|
311 | ||
312 |
assert resp.json['err'] |
|
313 |
assert resp.json['err_desc'] == 'Appointment not found' |
|
314 | ||
315 | ||
316 |
def test_manager(db, app, admin_user, connector): |
|
317 |
url = '/%s/%s/' % (connector.get_connector_slug(), connector.slug) |
|
318 |
connector.title = 'Test' |
|
319 |
connector.description = 'Test eSirius' |
|
320 |
connector.save() |
|
321 |
login(app) |
|
322 |
resp = app.get(url) |
|
323 |
resp = resp.click('edit') |
|
324 |
assert resp.html.find('div', {'id': 'id_secret_key_p'}).input['value'] == 'yyy' |
|
325 |
resp = resp.form.submit() |
|
326 |
assert ( |
|
327 |
'DES key must be 8 bytes long' |
|
328 |
in resp.html.find('div', {'id': 'id_secret_key_p'}).find('div', {'class': 'error'}).text |
|
329 |
) |
|
330 |
resp.form['secret_key'] = '8 bytes!' |
|
331 |
resp = resp.form.submit() |
|
332 |
assert ESirius.objects.get().secret_key == '8 bytes!' |
|
0 |
- |