Projet

Général

Profil

0001-esirius-add-e-sirius-connector-51365.patch

Nicolas Roche, 03 mars 2021 18:00

Télécharger (25,1 ko)

Voir les différences:

Subject: [PATCH] esirius: add e-sirius connector (#51365)

 passerelle/apps/esirius/__init__.py           |   0
 .../apps/esirius/migrations/0001_initial.py   |  84 +++++
 .../apps/esirius/migrations/__init__.py       |   0
 passerelle/apps/esirius/models.py             | 224 ++++++++++++
 passerelle/settings.py                        |   1 +
 tests/test_esirius.py                         | 332 ++++++++++++++++++
 6 files changed, 641 insertions(+)
 create mode 100644 passerelle/apps/esirius/__init__.py
 create mode 100644 passerelle/apps/esirius/migrations/0001_initial.py
 create mode 100644 passerelle/apps/esirius/migrations/__init__.py
 create mode 100644 passerelle/apps/esirius/models.py
 create mode 100644 tests/test_esirius.py
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
-