caluire-axel: child_schooling_info enpoint (#53853)

This commit is contained in:
Lauréline Guérin 2021-05-10 11:50:54 +02:00
parent 3c6a5fd797
commit 81c999a21d
No known key found for this signature in database
GPG Key ID: 1FAB9B9B4F93D473
9 changed files with 394 additions and 10 deletions

View File

@ -1,3 +1,4 @@
import datetime
import pprint
import requests
@ -48,6 +49,19 @@ def test_link(conn, user):
assert res['err'] == 0
print('\n')
print("and GET school info")
url = conn + '/child_schooling_info?NameID=%s&idpersonne=%s&booking_date=%s' % (
name_id,
child['IDENT'],
datetime.date.today().strftime('%Y-%m-%d'),
)
resp = requests.get(url)
resp.raise_for_status()
res = resp.json()
pprint.pprint(res)
assert res['err'] == 0
print('\n')
print("Deleting link")
url = conn + '/unlink?NameID=%s' % name_id
resp = requests.post(url)

View File

@ -14,6 +14,8 @@
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import datetime
from django.db import models
from django.utils.translation import ugettext_lazy as _
@ -22,7 +24,7 @@ from passerelle.contrib.utils import axel
from passerelle.utils.api import endpoint
from passerelle.utils.jsonresponse import APIError
from . import schemas
from . import schemas, utils
class CaluireAxel(BaseResource):
@ -32,7 +34,7 @@ class CaluireAxel(BaseResource):
)
category = _('Business Process Connectors')
_category_ordering = [_('Family account')]
_category_ordering = [_('Family account'), _('Schooling')]
class Meta:
verbose_name = _('Caluire Axel')
@ -150,6 +152,13 @@ class CaluireAxel(BaseResource):
return family_data
def get_child_data(self, family_id, child_id):
family_data = self.get_family_data(family_id)
for child in family_data.get('MEMBRE', []):
if child['IDENT'] == child_id:
return child
return None
@endpoint(
display_category=_('Family account'),
display_order=3,
@ -188,15 +197,51 @@ class CaluireAxel(BaseResource):
'idpersonne': {'description': _('Child ID')},
},
)
def child_info(self, request, idpersonne, NameID):
def child_info(self, request, NameID, idpersonne):
link = self.get_link(NameID)
family_data = self.get_family_data(link.family_id)
child_data = self.get_child_data(link.family_id, idpersonne)
if child_data is None:
raise APIError('Child not found', err_code='not-found')
return {'data': child_data}
for child in family_data.get('MEMBRE', []):
if child['IDENT'] == idpersonne:
return {'data': child}
@endpoint(
display_category=_('Schooling'),
display_order=1,
description=_("Get information about schooling of a child"),
perm='can_access',
parameters={
'NameID': {'description': _('Publik ID')},
'idpersonne': {'description': _('Child ID')},
'booking_date': {'description': _('Booking date (to get reference year)')},
},
)
def child_schooling_info(self, request, NameID, idpersonne, booking_date):
link = self.get_link(NameID)
try:
booking_date = datetime.datetime.strptime(booking_date, axel.json_date_format)
except ValueError:
raise APIError('bad date format, should be YYYY-MM-DD', err_code='bad-request', http_status=400)
raise APIError('Child not found', err_code='not-found')
child_data = self.get_child_data(link.family_id, idpersonne)
if child_data is None:
raise APIError('Child not found', err_code='not-found')
reference_year = utils.get_reference_year_from_date(booking_date)
try:
result = schemas.get_individu(
self,
{'PORTAIL': {'GETINDIVIDU': {'IDENTINDIVIDU': idpersonne, 'ANNEE': str(reference_year)}}},
)
except axel.AxelError as e:
raise APIError(
'Axel error: %s' % e,
err_code='error',
data={'xml_request': e.xml_request, 'xml_response': e.xml_response},
)
schooling_data = result.json_response['DATA']['PORTAIL']['GETINDIVIDU']
return {'data': schooling_data}
class Link(models.Model):

View File

@ -74,6 +74,7 @@ class Operation(axel.Operation):
find_individus = Operation('FindIndividus')
get_famille_individus = Operation('GetFamilleIndividus')
get_individu = Operation('GetIndividu')
LINK_SCHEMA = copy.deepcopy(

View File

@ -0,0 +1,23 @@
# -*- coding: utf-8 -*-
# passerelle - uniform access to multiple data sources and services
# Copyright (C) 2021 Entr'ouvert
#
# This program is free software: you can redistribute it and/or modify it
# under the terms of the GNU Affero General Public License as published
# by the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
def get_reference_year_from_date(booking_date):
if booking_date.month <= 8:
# between january and august, reference year is the year just before
return booking_date.year - 1
return booking_date.year

View File

@ -0,0 +1,26 @@
<?xml version="1.0" encoding="utf-8" ?>
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:all="urn:AllAxelTypes">
<xsd:import schemaLocation="./AllAxelTypes.xsd" namespace="urn:AllAxelTypes" />
<xsd:complexType name="PORTAILType">
<xsd:sequence>
<xsd:element ref="GETINDIVIDU" minOccurs="0" maxOccurs="1"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="GETINDIVIDUType">
<xsd:sequence>
<xsd:element ref="IDENTINDIVIDU" />
<xsd:element ref="ANNEE" />
</xsd:sequence>
</xsd:complexType>
<xsd:element name="IDENTINDIVIDU" type="all:IDENTREQUIREDType"/>
<xsd:element name="ANNEE" type="all:ANNEEType"/>
<xsd:element name="GETINDIVIDU" type="GETINDIVIDUType"/>
<xsd:element name="PORTAIL" type="PORTAILType"/>
</xsd:schema>

View File

@ -0,0 +1,63 @@
<?xml version="1.0" encoding="utf-8" ?>
<xsd:schema xmlns:all="urn:AllAxelTypes" xmlns:ind="urn:Individu" xmlns:xsd="http://www.w3.org/2001/XMLSchema" >
<xsd:import schemaLocation="./Individu.xsd" namespace="urn:Individu" />
<xsd:import schemaLocation="./AllAxelTypes.xsd" namespace="urn:AllAxelTypes" />
<xsd:redefine schemaLocation="./R_ShemaResultat.xsd">
<xsd:simpleType name="TYPEType">
<xsd:restriction base="TYPEType">
<xsd:enumeration value="GetIndividu" />
</xsd:restriction>
</xsd:simpleType>
<xsd:complexType name="PORTAILType">
<xsd:complexContent>
<xsd:extension base="PORTAILType">
<xsd:sequence>
<xsd:element ref="GETINDIVIDU" minOccurs="0" maxOccurs="1"/>
</xsd:sequence>
</xsd:extension>
</xsd:complexContent>
</xsd:complexType>
</xsd:redefine>
<xsd:complexType name="SCOLAIREType">
<xsd:sequence>
<xsd:element ref="IDENTECOLE" />
<xsd:element ref="LIBELLEECOLE"/>
<xsd:element ref="IDENTNIVEAU"/>
<xsd:element ref="LIBELLENIVEAU"/>
</xsd:sequence>
</xsd:complexType>
<xsd:simpleType name="PLACEType">
<xsd:restriction base="xsd:string">
<xsd:enumeration value="" />
<xsd:enumeration value="1" />
<xsd:enumeration value="2" />
<xsd:enumeration value="3" />
</xsd:restriction>
</xsd:simpleType>
<xsd:complexType name="GETINDIVIDUType">
<xsd:sequence>
<xsd:element ref="CODE" />
<xsd:element ref="SCOLAIRE" minOccurs="0" maxOccurs="1" />
<xsd:element ref="INDIVIDU" minOccurs="0" maxOccurs="1" />
</xsd:sequence>
</xsd:complexType>
<xsd:element name="CODE" type="xsd:integer"/>
<xsd:element name="INDIVIDU" type="ind:INDIVIDUType"/>
<xsd:element name="IDENTFAMILLE" type="all:IDENTType"/>
<xsd:element name="SCOLAIRE" type="SCOLAIREType"/>
<xsd:element name="PLACE" type="PLACEType"/>
<xsd:element name="IDENTECOLE" type="all:IDType"/>
<xsd:element name="IDENTNIVEAU" type="all:IDType"/>
<xsd:element name="LIBELLEECOLE" type="all:LIBELLEType"/>
<xsd:element name="LIBELLENIVEAU" type="all:LIBELLEType"/>
<xsd:element name="GETINDIVIDU" type="GETINDIVIDUType"/>
</xsd:schema>

View File

@ -0,0 +1,36 @@
<PORTAIL>
<GETINDIVIDU>
<CODE>0</CODE>
<SCOLAIRE>
<IDENTECOLE>ECOLE1</IDENTECOLE>
<LIBELLEECOLE>Ecole El&#233;mentaire</LIBELLEECOLE>
<IDENTNIVEAU>CE2</IDENTNIVEAU>
<LIBELLENIVEAU>Cours &#233;l&#233;mentaire 2&#232;me ann&#233;e</LIBELLENIVEAU>
</SCOLAIRE>
<INDIVIDU>
<IDENT>50632</IDENT>
<CIVILITE />
<NOM>CALUIRE TEST</NOM>
<PRENOM>Enfant 1 </PRENOM>
<NAISSANCE>10/10/2013</NAISSANCE>
<SEXE>M</SEXE>
<NOMJF />
<TELFIXE />
<TELPORTABLE />
<MAIL />
<PAI>N</PAI>
<GARDEALTERNEE>O</GARDEALTERNEE>
<ADRESSE>
<ADRESSE3 />
<ADRESSE4 />
<NORUE>30</NORUE>
<ADRESSE1>RUE PASTEUR</ADRESSE1>
<ADRESSE2 />
<CODEPOSTAL>69300</CODEPOSTAL>
<VILLE>CALUIRE ET CUIRE</VILLE>
<PAYS />
<NPAI />
</ADRESSE>
</INDIVIDU>
</GETINDIVIDU>
</PORTAIL>

View File

@ -16,6 +16,7 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import os
import xml.etree.ElementTree as ET
from contextlib import contextmanager
import mock
@ -45,6 +46,33 @@ def link_params():
}
@pytest.fixture
def family_data():
filepath = os.path.join(os.path.dirname(__file__), 'data/caluire_axel/family_info.xml')
with open(filepath) as xml:
content = xml.read()
resp = (
'''
<?xml version="1.0"?>
<PORTAILSERVICE>
<RESULTAT>
<TYPE>GetFamilleIndividus</TYPE>
<STATUS>OK</STATUS>
<DATE>10/10/2010 10:10:01</DATE>
<COMMENTAIRES><![CDATA[]]></COMMENTAIRES>
</RESULTAT>
<DATA>
%s
</DATA>
</PORTAILSERVICE>
'''.strip()
% content
)
return schemas.get_famille_individus.response_converter.decode(ET.fromstring(resp))['DATA']['PORTAIL'][
'GETFAMILLE'
]
@contextmanager
def mock_getdata(content, operation):
with mock.patch('passerelle.contrib.caluire_axel.models.CaluireAxel.soap_client') as client:
@ -131,6 +159,48 @@ def test_operation_find_individus(resource, content):
)
@pytest.mark.parametrize(
'content',
[
'<PORTAIL><GETFAMILLE/></PORTAIL>',
],
)
def test_operation_get_famille_individus(resource, content):
with mock_getdata(content, 'GetFamilleIndividus'):
with pytest.raises(AxelError):
schemas.get_famille_individus(
resource,
{
'PORTAIL': {
'GETFAMILLE': {
'IDENTFAMILLE': 'XXX',
}
}
},
)
@pytest.mark.parametrize(
'content',
[
'<PORTAIL><GETINDIVIDU/></PORTAIL>',
],
)
def test_operation_get_individu(resource, content):
with mock_getdata(content, 'GetIndividu'):
with pytest.raises(AxelError):
schemas.get_individu(
resource,
{
'PORTAIL': {
'GETINDIVIDU': {
'IDENTINDIVIDU': 'XXX',
}
}
},
)
def test_link_endpoint_nameid_empty(app, resource, link_params):
resp = app.post_json('/caluire-axel/test/link?NameID=', params=link_params, status=400)
assert resp.json['err_desc'] == "NameID is empty"
@ -440,13 +510,13 @@ def test_child_info_endpoint_axel_error(app, resource):
Link.objects.create(resource=resource, name_id='yyy', family_id='XXX', person_id='42')
with mock.patch('passerelle.contrib.caluire_axel.schemas.get_famille_individus') as operation:
operation.side_effect = AxelError('FooBar')
resp = app.get('/caluire-axel/test/child_info?NameID=yyy&idpersonne=zzz')
resp = app.get('/caluire-axel/test/child_info?NameID=yyy&idpersonne=50632')
assert resp.json['err_desc'] == "Axel error: FooBar"
assert resp.json['err'] == 'error'
def test_child_info_endpoint_no_result(app, resource):
resp = app.get('/caluire-axel/test/child_info?NameID=yyy&idpersonne=zzz')
resp = app.get('/caluire-axel/test/child_info?NameID=yyy&idpersonne=50632')
assert resp.json['err_desc'] == "Person not found"
assert resp.json['err'] == 'not-found'
@ -490,3 +560,73 @@ def test_child_info_endpoint(app, resource):
)
assert resp.json['data']['id'] == '50632'
assert resp.json['data']['text'] == 'Enfant 1 CALUIRE TEST'
def test_child_schooling_info_endpoint_axel_error(app, resource):
Link.objects.create(resource=resource, name_id='yyy', family_id='XXX', person_id='42')
with mock.patch('passerelle.contrib.caluire_axel.schemas.get_famille_individus') as operation:
operation.side_effect = AxelError('FooBar')
resp = app.get(
'/caluire-axel/test/child_schooling_info?NameID=yyy&idpersonne=50632&booking_date=2021-05-10'
)
assert resp.json['err_desc'] == "Axel error: FooBar"
assert resp.json['err'] == 'error'
filepath = os.path.join(os.path.dirname(__file__), 'data/caluire_axel/family_info.xml')
with open(filepath) as xml:
content = xml.read()
with mock_getdata(content, 'GetFamilleIndividus'):
with mock.patch('passerelle.contrib.caluire_axel.schemas.get_individu') as operation:
operation.side_effect = AxelError('FooBar')
resp = app.get(
'/caluire-axel/test/child_schooling_info?NameID=yyy&idpersonne=50632&booking_date=2021-05-10'
)
assert resp.json['err_desc'] == "Axel error: FooBar"
assert resp.json['err'] == 'error'
@pytest.mark.parametrize('value', ['foo', '20/01/2020', '2020'])
def test_child_schooling_info_endpoint_bad_date_format(app, resource, value):
Link.objects.create(resource=resource, name_id='yyy', family_id='XXX', person_id='42')
resp = app.get(
'/caluire-axel/test/child_schooling_info?NameID=yyy&idpersonne=50632&booking_date=%s' % value,
status=400,
)
assert resp.json['err_desc'] == "bad date format, should be YYYY-MM-DD"
assert resp.json['err'] == 'bad-request'
def test_child_schooling_info_endpoint_no_result(app, resource):
resp = app.get(
'/caluire-axel/test/child_schooling_info?NameID=yyy&idpersonne=50632&booking_date=2021-05-10'
)
assert resp.json['err_desc'] == "Person not found"
assert resp.json['err'] == 'not-found'
Link.objects.create(resource=resource, name_id='yyy', family_id='XXX', person_id='42')
filepath = os.path.join(os.path.dirname(__file__), 'data/caluire_axel/family_info.xml')
with open(filepath) as xml:
content = xml.read()
with mock_getdata(content, 'GetFamilleIndividus'):
resp = app.get(
'/caluire-axel/test/child_schooling_info?NameID=yyy&idpersonne=zzz&booking_date=2021-05-10'
)
assert resp.json['err_desc'] == "Child not found"
assert resp.json['err'] == 'not-found'
def test_child_schooling_info(app, resource, family_data):
Link.objects.create(resource=resource, name_id='yyy', family_id='XXX', person_id='42')
filepath = os.path.join(os.path.dirname(__file__), 'data/caluire_axel/schooling_info.xml')
with open(filepath) as xml:
content = xml.read()
with mock_getdata(content, 'GetIndividu'):
with mock.patch(
'passerelle.contrib.caluire_axel.models.CaluireAxel.get_family_data',
return_value=family_data,
):
resp = app.get(
'/caluire-axel/test/child_schooling_info?NameID=yyy&idpersonne=50632&booking_date=2021-05-10'
)
assert resp.json['err'] == 0
assert set(resp.json['data'].keys()) == set(['CODE', 'INDIVIDU', 'SCOLAIRE'])

View File

@ -0,0 +1,36 @@
# passerelle - uniform access to multiple data sources and services
# Copyright (C) 2021 Entr'ouvert
#
# This program is free software: you can redistribute it and/or modify it
# under the terms of the GNU Affero General Public License as published
# by the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import datetime
import pytest
from passerelle.contrib.caluire_axel.utils import get_reference_year_from_date
from passerelle.contrib.utils.axel import json_date_format
@pytest.mark.parametrize(
'value, expected',
[
('2021-01-01', 2020),
('2021-08-31', 2020),
('2021-09-01', 2021),
('2021-12-31', 2021),
],
)
def test_get_reference_year_from_date(value, expected):
in_date = datetime.datetime.strptime(value, json_date_format)
assert get_reference_year_from_date(in_date) == expected