Projet

Général

Profil

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

Nicolas Roche, 04 mars 2021 11:48

Télécharger (25 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             | 231 +++++++++++++
 passerelle/settings.py                        |   1 +
 tests/test_esirius.py                         | 314 ++++++++++++++++++
 6 files changed, 630 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 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
-