From 81c999a21df9e50c68fdba0a2f0f8f1f0b20a321 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Laur=C3=A9line=20Gu=C3=A9rin?= Date: Mon, 10 May 2021 11:50:54 +0200 Subject: [PATCH] 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 diff --git a/functests/caluire_axel/test_caluire_axel.py b/functests/caluire_axel/test_caluire_axel.py index 20c881f8..8c0f91d2 100644 --- a/functests/caluire_axel/test_caluire_axel.py +++ b/functests/caluire_axel/test_caluire_axel.py @@ -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) diff --git a/passerelle/contrib/caluire_axel/models.py b/passerelle/contrib/caluire_axel/models.py index de3b0b1a..22ede3b4 100644 --- a/passerelle/contrib/caluire_axel/models.py +++ b/passerelle/contrib/caluire_axel/models.py @@ -14,6 +14,8 @@ # You should have received a copy of the GNU Affero General Public License # along with this program. If not, see . +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): diff --git a/passerelle/contrib/caluire_axel/schemas.py b/passerelle/contrib/caluire_axel/schemas.py index f1d579d8..eba5b858 100644 --- a/passerelle/contrib/caluire_axel/schemas.py +++ b/passerelle/contrib/caluire_axel/schemas.py @@ -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( diff --git a/passerelle/contrib/caluire_axel/utils.py b/passerelle/contrib/caluire_axel/utils.py new file mode 100644 index 00000000..49a3377d --- /dev/null +++ b/passerelle/contrib/caluire_axel/utils.py @@ -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 . + + +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 diff --git a/passerelle/contrib/caluire_axel/xsd/Q_GetIndividu.xsd b/passerelle/contrib/caluire_axel/xsd/Q_GetIndividu.xsd new file mode 100644 index 00000000..e4248a49 --- /dev/null +++ b/passerelle/contrib/caluire_axel/xsd/Q_GetIndividu.xsd @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/passerelle/contrib/caluire_axel/xsd/R_GetIndividu.xsd b/passerelle/contrib/caluire_axel/xsd/R_GetIndividu.xsd new file mode 100644 index 00000000..8768ae75 --- /dev/null +++ b/passerelle/contrib/caluire_axel/xsd/R_GetIndividu.xsd @@ -0,0 +1,63 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tests/data/caluire_axel/schooling_info.xml b/tests/data/caluire_axel/schooling_info.xml new file mode 100644 index 00000000..b46d872a --- /dev/null +++ b/tests/data/caluire_axel/schooling_info.xml @@ -0,0 +1,36 @@ + + + 0 + + ECOLE1 + Ecole Elémentaire + CE2 + Cours élémentaire 2ème année + + + 50632 + + CALUIRE TEST + Enfant 1 + 10/10/2013 + M + + + + + N + O + + + + 30 + RUE PASTEUR + + 69300 + CALUIRE ET CUIRE + + + + + + diff --git a/tests/test_caluire_axel.py b/tests/test_caluire_axel.py index 637558e3..49d80aa7 100644 --- a/tests/test_caluire_axel.py +++ b/tests/test_caluire_axel.py @@ -16,6 +16,7 @@ # along with this program. If not, see . 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 = ( + ''' + + + + GetFamilleIndividus + OK + 10/10/2010 10:10:01 + + + + %s + + + '''.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', + [ + '', + ], +) +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', + [ + '', + ], +) +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']) diff --git a/tests/test_caluire_axel_utils.py b/tests/test_caluire_axel_utils.py new file mode 100644 index 00000000..d4dc5207 --- /dev/null +++ b/tests/test_caluire_axel_utils.py @@ -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 . + +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