Projet

Général

Profil

0001-caluire-axel-child_schooling_info-enpoint-53853.patch

Lauréline Guérin, 10 mai 2021 11:58

Télécharger (20,7 ko)

Voir les différences:

Subject: [PATCH 1/2] caluire-axel: child_schooling_info enpoint (#53853)

 functests/caluire_axel/test_caluire_axel.py   |  14 ++
 passerelle/contrib/caluire_axel/models.py     |  61 +++++++-
 passerelle/contrib/caluire_axel/schemas.py    |   1 +
 passerelle/contrib/caluire_axel/utils.py      |  23 +++
 .../caluire_axel/xsd/Q_GetIndividu.xsd        |  26 ++++
 .../caluire_axel/xsd/R_GetIndividu.xsd        |  63 ++++++++
 tests/data/caluire_axel/schooling_info.xml    |  36 +++++
 tests/test_caluire_axel.py                    | 144 +++++++++++++++++-
 tests/test_caluire_axel_utils.py              |  36 +++++
 9 files changed, 394 insertions(+), 10 deletions(-)
 create mode 100644 passerelle/contrib/caluire_axel/utils.py
 create mode 100644 passerelle/contrib/caluire_axel/xsd/Q_GetIndividu.xsd
 create mode 100644 passerelle/contrib/caluire_axel/xsd/R_GetIndividu.xsd
 create mode 100644 tests/data/caluire_axel/schooling_info.xml
 create mode 100644 tests/test_caluire_axel_utils.py
functests/caluire_axel/test_caluire_axel.py
1
import datetime
1 2
import pprint
2 3

  
3 4
import requests
......
48 49
        assert res['err'] == 0
49 50
        print('\n')
50 51

  
52
        print("and GET school info")
53
        url = conn + '/child_schooling_info?NameID=%s&idpersonne=%s&booking_date=%s' % (
54
            name_id,
55
            child['IDENT'],
56
            datetime.date.today().strftime('%Y-%m-%d'),
57
        )
58
        resp = requests.get(url)
59
        resp.raise_for_status()
60
        res = resp.json()
61
        pprint.pprint(res)
62
        assert res['err'] == 0
63
        print('\n')
64

  
51 65
    print("Deleting link")
52 66
    url = conn + '/unlink?NameID=%s' % name_id
53 67
    resp = requests.post(url)
passerelle/contrib/caluire_axel/models.py
14 14
# You should have received a copy of the GNU Affero General Public License
15 15
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
16 16

  
17
import datetime
18

  
17 19
from django.db import models
18 20
from django.utils.translation import ugettext_lazy as _
19 21

  
......
22 24
from passerelle.utils.api import endpoint
23 25
from passerelle.utils.jsonresponse import APIError
24 26

  
25
from . import schemas
27
from . import schemas, utils
26 28

  
27 29

  
28 30
class CaluireAxel(BaseResource):
......
32 34
    )
33 35

  
34 36
    category = _('Business Process Connectors')
35
    _category_ordering = [_('Family account')]
37
    _category_ordering = [_('Family account'), _('Schooling')]
36 38

  
37 39
    class Meta:
38 40
        verbose_name = _('Caluire Axel')
......
150 152

  
151 153
        return family_data
152 154

  
155
    def get_child_data(self, family_id, child_id):
156
        family_data = self.get_family_data(family_id)
157
        for child in family_data.get('MEMBRE', []):
158
            if child['IDENT'] == child_id:
159
                return child
160
        return None
161

  
153 162
    @endpoint(
154 163
        display_category=_('Family account'),
155 164
        display_order=3,
......
188 197
            'idpersonne': {'description': _('Child ID')},
189 198
        },
190 199
    )
191
    def child_info(self, request, idpersonne, NameID):
200
    def child_info(self, request, NameID, idpersonne):
192 201
        link = self.get_link(NameID)
193
        family_data = self.get_family_data(link.family_id)
202
        child_data = self.get_child_data(link.family_id, idpersonne)
203
        if child_data is None:
204
            raise APIError('Child not found', err_code='not-found')
205
        return {'data': child_data}
194 206

  
195
        for child in family_data.get('MEMBRE', []):
196
            if child['IDENT'] == idpersonne:
197
                return {'data': child}
207
    @endpoint(
208
        display_category=_('Schooling'),
209
        display_order=1,
210
        description=_("Get information about schooling of a child"),
211
        perm='can_access',
212
        parameters={
213
            'NameID': {'description': _('Publik ID')},
214
            'idpersonne': {'description': _('Child ID')},
215
            'booking_date': {'description': _('Booking date (to get reference year)')},
216
        },
217
    )
218
    def child_schooling_info(self, request, NameID, idpersonne, booking_date):
219
        link = self.get_link(NameID)
220
        try:
221
            booking_date = datetime.datetime.strptime(booking_date, axel.json_date_format)
222
        except ValueError:
223
            raise APIError('bad date format, should be YYYY-MM-DD', err_code='bad-request', http_status=400)
224

  
225
        child_data = self.get_child_data(link.family_id, idpersonne)
226
        if child_data is None:
227
            raise APIError('Child not found', err_code='not-found')
228

  
229
        reference_year = utils.get_reference_year_from_date(booking_date)
230
        try:
231
            result = schemas.get_individu(
232
                self,
233
                {'PORTAIL': {'GETINDIVIDU': {'IDENTINDIVIDU': idpersonne, 'ANNEE': str(reference_year)}}},
234
            )
235
        except axel.AxelError as e:
236
            raise APIError(
237
                'Axel error: %s' % e,
238
                err_code='error',
239
                data={'xml_request': e.xml_request, 'xml_response': e.xml_response},
240
            )
241

  
242
        schooling_data = result.json_response['DATA']['PORTAIL']['GETINDIVIDU']
198 243

  
199
        raise APIError('Child not found', err_code='not-found')
244
        return {'data': schooling_data}
200 245

  
201 246

  
202 247
class Link(models.Model):
passerelle/contrib/caluire_axel/schemas.py
74 74

  
75 75
find_individus = Operation('FindIndividus')
76 76
get_famille_individus = Operation('GetFamilleIndividus')
77
get_individu = Operation('GetIndividu')
77 78

  
78 79

  
79 80
LINK_SCHEMA = copy.deepcopy(
passerelle/contrib/caluire_axel/utils.py
1
# -*- coding: utf-8 -*-
2
# passerelle - uniform access to multiple data sources and services
3
# Copyright (C) 2021  Entr'ouvert
4
#
5
# This program is free software: you can redistribute it and/or modify it
6
# under the terms of the GNU Affero General Public License as published
7
# by the Free Software Foundation, either version 3 of the License, or
8
# (at your option) any later version.
9
#
10
# This program is distributed in the hope that it will be useful,
11
# but WITHOUT ANY WARRANTY; without even the implied warranty of
12
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13
# GNU Affero General Public License for more details.
14
#
15
# You should have received a copy of the GNU Affero General Public License
16
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
17

  
18

  
19
def get_reference_year_from_date(booking_date):
20
    if booking_date.month <= 8:
21
        # between january and august, reference year is the year just before
22
        return booking_date.year - 1
23
    return booking_date.year
passerelle/contrib/caluire_axel/xsd/Q_GetIndividu.xsd
1
<?xml version="1.0" encoding="utf-8" ?>
2
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:all="urn:AllAxelTypes">
3
	
4
	<xsd:import schemaLocation="./AllAxelTypes.xsd" namespace="urn:AllAxelTypes"  />
5
	
6
	<xsd:complexType name="PORTAILType">
7
		<xsd:sequence>
8
			<xsd:element ref="GETINDIVIDU" minOccurs="0" maxOccurs="1"/>
9
		</xsd:sequence>  
10
	</xsd:complexType>
11
	
12
	<xsd:complexType name="GETINDIVIDUType">
13
		<xsd:sequence>
14
			<xsd:element ref="IDENTINDIVIDU" />
15
			<xsd:element ref="ANNEE" />
16
		</xsd:sequence> 
17
	</xsd:complexType>
18
	
19
	<xsd:element name="IDENTINDIVIDU" type="all:IDENTREQUIREDType"/>
20
	<xsd:element name="ANNEE" type="all:ANNEEType"/>
21
	
22
	<xsd:element name="GETINDIVIDU" type="GETINDIVIDUType"/>
23
	
24
	<xsd:element name="PORTAIL" type="PORTAILType"/>	
25
		
26
</xsd:schema>
passerelle/contrib/caluire_axel/xsd/R_GetIndividu.xsd
1
<?xml version="1.0" encoding="utf-8" ?>
2
<xsd:schema	xmlns:all="urn:AllAxelTypes" xmlns:ind="urn:Individu"  xmlns:xsd="http://www.w3.org/2001/XMLSchema" >
3
	
4
	<xsd:import schemaLocation="./Individu.xsd" namespace="urn:Individu" />
5
	<xsd:import schemaLocation="./AllAxelTypes.xsd" namespace="urn:AllAxelTypes" />
6
	
7
	<xsd:redefine schemaLocation="./R_ShemaResultat.xsd">
8
	    <xsd:simpleType name="TYPEType">
9
			<xsd:restriction base="TYPEType">
10
				<xsd:enumeration value="GetIndividu" />
11
			</xsd:restriction>
12
	    </xsd:simpleType>
13
		
14
		<xsd:complexType name="PORTAILType">
15
			<xsd:complexContent>
16
				<xsd:extension base="PORTAILType">
17
					<xsd:sequence>
18
							<xsd:element ref="GETINDIVIDU" minOccurs="0" maxOccurs="1"/>
19
					</xsd:sequence>
20
				</xsd:extension>
21
			 </xsd:complexContent>
22
		</xsd:complexType>
23
	</xsd:redefine>	
24
	
25
	<xsd:complexType name="SCOLAIREType">
26
		<xsd:sequence>
27
			<xsd:element ref="IDENTECOLE" />
28
			<xsd:element ref="LIBELLEECOLE"/>
29
			<xsd:element ref="IDENTNIVEAU"/>
30
			<xsd:element ref="LIBELLENIVEAU"/>
31
		</xsd:sequence> 
32
	</xsd:complexType>	
33
	
34
	<xsd:simpleType name="PLACEType">
35
		<xsd:restriction base="xsd:string">
36
			<xsd:enumeration value="" />
37
			<xsd:enumeration value="1" />
38
			<xsd:enumeration value="2" />
39
			<xsd:enumeration value="3" />
40
		</xsd:restriction>
41
	</xsd:simpleType>
42
	
43
	<xsd:complexType name="GETINDIVIDUType">
44
		<xsd:sequence>
45
			<xsd:element ref="CODE" />
46
			<xsd:element ref="SCOLAIRE" minOccurs="0" maxOccurs="1" />
47
			<xsd:element ref="INDIVIDU" minOccurs="0" maxOccurs="1" />
48
		</xsd:sequence> 
49
	</xsd:complexType>
50
	
51
	<xsd:element name="CODE" type="xsd:integer"/>
52
	<xsd:element name="INDIVIDU" type="ind:INDIVIDUType"/>
53
	<xsd:element name="IDENTFAMILLE" type="all:IDENTType"/>
54
	<xsd:element name="SCOLAIRE" type="SCOLAIREType"/>
55
	<xsd:element name="PLACE" type="PLACEType"/>
56
	<xsd:element name="IDENTECOLE" type="all:IDType"/>
57
	<xsd:element name="IDENTNIVEAU" type="all:IDType"/>
58
	<xsd:element name="LIBELLEECOLE" type="all:LIBELLEType"/>
59
	<xsd:element name="LIBELLENIVEAU" type="all:LIBELLEType"/>
60
	
61
	<xsd:element name="GETINDIVIDU" type="GETINDIVIDUType"/>
62
		
63
</xsd:schema>
tests/data/caluire_axel/schooling_info.xml
1
<PORTAIL>
2
  <GETINDIVIDU>
3
    <CODE>0</CODE>
4
    <SCOLAIRE>
5
      <IDENTECOLE>ECOLE1</IDENTECOLE>
6
      <LIBELLEECOLE>Ecole El&#233;mentaire</LIBELLEECOLE>
7
      <IDENTNIVEAU>CE2</IDENTNIVEAU>
8
      <LIBELLENIVEAU>Cours &#233;l&#233;mentaire 2&#232;me ann&#233;e</LIBELLENIVEAU>
9
    </SCOLAIRE>
10
    <INDIVIDU>
11
      <IDENT>50632</IDENT>
12
      <CIVILITE />
13
      <NOM>CALUIRE TEST</NOM>
14
      <PRENOM>Enfant 1 </PRENOM>
15
      <NAISSANCE>10/10/2013</NAISSANCE>
16
      <SEXE>M</SEXE>
17
      <NOMJF />
18
      <TELFIXE />
19
      <TELPORTABLE />
20
      <MAIL />
21
      <PAI>N</PAI>
22
      <GARDEALTERNEE>O</GARDEALTERNEE>
23
      <ADRESSE>
24
        <ADRESSE3 />
25
        <ADRESSE4 />
26
        <NORUE>30</NORUE>
27
        <ADRESSE1>RUE PASTEUR</ADRESSE1>
28
        <ADRESSE2 />
29
        <CODEPOSTAL>69300</CODEPOSTAL>
30
        <VILLE>CALUIRE ET CUIRE</VILLE>
31
        <PAYS />
32
        <NPAI />
33
      </ADRESSE>
34
    </INDIVIDU>
35
  </GETINDIVIDU>
36
</PORTAIL>
tests/test_caluire_axel.py
16 16
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
17 17

  
18 18
import os
19
import xml.etree.ElementTree as ET
19 20
from contextlib import contextmanager
20 21

  
21 22
import mock
......
45 46
    }
46 47

  
47 48

  
49
@pytest.fixture
50
def family_data():
51
    filepath = os.path.join(os.path.dirname(__file__), 'data/caluire_axel/family_info.xml')
52
    with open(filepath) as xml:
53
        content = xml.read()
54
    resp = (
55
        '''
56
    <?xml version="1.0"?>
57
    <PORTAILSERVICE>
58
      <RESULTAT>
59
        <TYPE>GetFamilleIndividus</TYPE>
60
        <STATUS>OK</STATUS>
61
        <DATE>10/10/2010 10:10:01</DATE>
62
        <COMMENTAIRES><![CDATA[]]></COMMENTAIRES>
63
      </RESULTAT>
64
      <DATA>
65
        %s
66
      </DATA>
67
    </PORTAILSERVICE>
68
    '''.strip()
69
        % content
70
    )
71
    return schemas.get_famille_individus.response_converter.decode(ET.fromstring(resp))['DATA']['PORTAIL'][
72
        'GETFAMILLE'
73
    ]
74

  
75

  
48 76
@contextmanager
49 77
def mock_getdata(content, operation):
50 78
    with mock.patch('passerelle.contrib.caluire_axel.models.CaluireAxel.soap_client') as client:
......
131 159
            )
132 160

  
133 161

  
162
@pytest.mark.parametrize(
163
    'content',
164
    [
165
        '<PORTAIL><GETFAMILLE/></PORTAIL>',
166
    ],
167
)
168
def test_operation_get_famille_individus(resource, content):
169
    with mock_getdata(content, 'GetFamilleIndividus'):
170
        with pytest.raises(AxelError):
171
            schemas.get_famille_individus(
172
                resource,
173
                {
174
                    'PORTAIL': {
175
                        'GETFAMILLE': {
176
                            'IDENTFAMILLE': 'XXX',
177
                        }
178
                    }
179
                },
180
            )
181

  
182

  
183
@pytest.mark.parametrize(
184
    'content',
185
    [
186
        '<PORTAIL><GETINDIVIDU/></PORTAIL>',
187
    ],
188
)
189
def test_operation_get_individu(resource, content):
190
    with mock_getdata(content, 'GetIndividu'):
191
        with pytest.raises(AxelError):
192
            schemas.get_individu(
193
                resource,
194
                {
195
                    'PORTAIL': {
196
                        'GETINDIVIDU': {
197
                            'IDENTINDIVIDU': 'XXX',
198
                        }
199
                    }
200
                },
201
            )
202

  
203

  
134 204
def test_link_endpoint_nameid_empty(app, resource, link_params):
135 205
    resp = app.post_json('/caluire-axel/test/link?NameID=', params=link_params, status=400)
136 206
    assert resp.json['err_desc'] == "NameID is empty"
......
440 510
    Link.objects.create(resource=resource, name_id='yyy', family_id='XXX', person_id='42')
441 511
    with mock.patch('passerelle.contrib.caluire_axel.schemas.get_famille_individus') as operation:
442 512
        operation.side_effect = AxelError('FooBar')
443
        resp = app.get('/caluire-axel/test/child_info?NameID=yyy&idpersonne=zzz')
513
        resp = app.get('/caluire-axel/test/child_info?NameID=yyy&idpersonne=50632')
444 514
    assert resp.json['err_desc'] == "Axel error: FooBar"
445 515
    assert resp.json['err'] == 'error'
446 516

  
447 517

  
448 518
def test_child_info_endpoint_no_result(app, resource):
449
    resp = app.get('/caluire-axel/test/child_info?NameID=yyy&idpersonne=zzz')
519
    resp = app.get('/caluire-axel/test/child_info?NameID=yyy&idpersonne=50632')
450 520
    assert resp.json['err_desc'] == "Person not found"
451 521
    assert resp.json['err'] == 'not-found'
452 522

  
......
490 560
    )
491 561
    assert resp.json['data']['id'] == '50632'
492 562
    assert resp.json['data']['text'] == 'Enfant 1 CALUIRE TEST'
563

  
564

  
565
def test_child_schooling_info_endpoint_axel_error(app, resource):
566
    Link.objects.create(resource=resource, name_id='yyy', family_id='XXX', person_id='42')
567
    with mock.patch('passerelle.contrib.caluire_axel.schemas.get_famille_individus') as operation:
568
        operation.side_effect = AxelError('FooBar')
569
        resp = app.get(
570
            '/caluire-axel/test/child_schooling_info?NameID=yyy&idpersonne=50632&booking_date=2021-05-10'
571
        )
572
    assert resp.json['err_desc'] == "Axel error: FooBar"
573
    assert resp.json['err'] == 'error'
574

  
575
    filepath = os.path.join(os.path.dirname(__file__), 'data/caluire_axel/family_info.xml')
576
    with open(filepath) as xml:
577
        content = xml.read()
578
    with mock_getdata(content, 'GetFamilleIndividus'):
579
        with mock.patch('passerelle.contrib.caluire_axel.schemas.get_individu') as operation:
580
            operation.side_effect = AxelError('FooBar')
581
            resp = app.get(
582
                '/caluire-axel/test/child_schooling_info?NameID=yyy&idpersonne=50632&booking_date=2021-05-10'
583
            )
584
    assert resp.json['err_desc'] == "Axel error: FooBar"
585
    assert resp.json['err'] == 'error'
586

  
587

  
588
@pytest.mark.parametrize('value', ['foo', '20/01/2020', '2020'])
589
def test_child_schooling_info_endpoint_bad_date_format(app, resource, value):
590
    Link.objects.create(resource=resource, name_id='yyy', family_id='XXX', person_id='42')
591
    resp = app.get(
592
        '/caluire-axel/test/child_schooling_info?NameID=yyy&idpersonne=50632&booking_date=%s' % value,
593
        status=400,
594
    )
595
    assert resp.json['err_desc'] == "bad date format, should be YYYY-MM-DD"
596
    assert resp.json['err'] == 'bad-request'
597

  
598

  
599
def test_child_schooling_info_endpoint_no_result(app, resource):
600
    resp = app.get(
601
        '/caluire-axel/test/child_schooling_info?NameID=yyy&idpersonne=50632&booking_date=2021-05-10'
602
    )
603
    assert resp.json['err_desc'] == "Person not found"
604
    assert resp.json['err'] == 'not-found'
605

  
606
    Link.objects.create(resource=resource, name_id='yyy', family_id='XXX', person_id='42')
607
    filepath = os.path.join(os.path.dirname(__file__), 'data/caluire_axel/family_info.xml')
608
    with open(filepath) as xml:
609
        content = xml.read()
610
    with mock_getdata(content, 'GetFamilleIndividus'):
611
        resp = app.get(
612
            '/caluire-axel/test/child_schooling_info?NameID=yyy&idpersonne=zzz&booking_date=2021-05-10'
613
        )
614
    assert resp.json['err_desc'] == "Child not found"
615
    assert resp.json['err'] == 'not-found'
616

  
617

  
618
def test_child_schooling_info(app, resource, family_data):
619
    Link.objects.create(resource=resource, name_id='yyy', family_id='XXX', person_id='42')
620
    filepath = os.path.join(os.path.dirname(__file__), 'data/caluire_axel/schooling_info.xml')
621
    with open(filepath) as xml:
622
        content = xml.read()
623
    with mock_getdata(content, 'GetIndividu'):
624
        with mock.patch(
625
            'passerelle.contrib.caluire_axel.models.CaluireAxel.get_family_data',
626
            return_value=family_data,
627
        ):
628
            resp = app.get(
629
                '/caluire-axel/test/child_schooling_info?NameID=yyy&idpersonne=50632&booking_date=2021-05-10'
630
            )
631
    assert resp.json['err'] == 0
632
    assert set(resp.json['data'].keys()) == set(['CODE', 'INDIVIDU', 'SCOLAIRE'])
tests/test_caluire_axel_utils.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 datetime
18

  
19
import pytest
20

  
21
from passerelle.contrib.caluire_axel.utils import get_reference_year_from_date
22
from passerelle.contrib.utils.axel import json_date_format
23

  
24

  
25
@pytest.mark.parametrize(
26
    'value, expected',
27
    [
28
        ('2021-01-01', 2020),
29
        ('2021-08-31', 2020),
30
        ('2021-09-01', 2021),
31
        ('2021-12-31', 2021),
32
    ],
33
)
34
def test_get_reference_year_from_date(value, expected):
35
    in_date = datetime.datetime.strptime(value, json_date_format)
36
    assert get_reference_year_from_date(in_date) == expected
0
-